/* -------------------------------------------------------------------------------

This code is based on source provided under the terms of the Id Software 
LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the
GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of 
LICENSE_ID, please contact Id Software immediately at info@idsoftware.com.

All changes and additions to the original source which have been developed by
other contributors (see CONTRIBUTORS) are provided under the terms of the
license the contributors choose (see LICENSE), to the extent permitted by the
LICENSE_ID. If you did not receive a copy of the contributor license,
please contact the GtkRadiant maintainers at info@gtkradiant.com immediately.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------------------

This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."

------------------------------------------------------------------------------- */



/* marker */
#define LIGHT_YDNAR_C



/* dependencies */
#include "q3map2.h"




/*
ColorToBytes()
ydnar: moved to here 2001-02-04
*/

void ColorToBytes( const float *color, byte *colorBytes, float scale )
{
	int		i;
	float	max, gamma;
	vec3_t	sample;
	float 	inv, dif;
	
	
	/* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */
	if( scale <= 0.0f )
		scale = 1.0f;
	
	/* make a local copy */
	VectorScale( color, scale, sample );
	
	/* muck with it */
	gamma = 1.0f / lightmapGamma;
	for( i = 0; i < 3; i++ )
	{
		/* handle negative light */
		if( sample[ i ] < 0.0f )
		{
			sample[ i ] = 0.0f;
			continue;
		}
		
		/* gamma */
		sample[ i ] = pow( sample[ i ] / 255.0f, gamma ) * 255.0f;
	}

	if (lightmapExposure == 1)
	{
		/* clamp with color normalization */
		max = sample[ 0 ];
		if( sample[ 1 ] > max )
			max = sample[ 1 ];
		if( sample[ 2 ] > max )
			max = sample[ 2 ];
		if( max > 255.0f )
			VectorScale( sample, (255.0f / max), sample );
	}
	else
	{
		if (lightmapExposure==0)
		{
			lightmapExposure=1.0f;
		}
		inv=1.f/lightmapExposure;
		//Exposure
    	
		max = sample[ 0 ];
		if( sample[ 1 ] > max )
			max = sample[ 1 ];
		if( sample[ 2 ] > max )
			max = sample[ 2 ];  
      
		dif = (1-  exp(-max * inv) )  *  255;

		if (max >0) 
		{
			dif = dif / max;
		}
		else
		{
			dif = 0;
		}

		for (i=0;i<3;i++)
		{
			sample[i]*=dif;
		}
	}
	
	
	/* compensate for ingame overbrighting/bitshifting */
	VectorScale( sample, (1.0f / lightmapCompensate), sample );
	
	//Compensate for that radbump samples are darker then they should be



	/* store it off */
	/*if ((sample[0]<0) ||(sample[0]>255) )
	{ 
		sample[0]=0;
	}
	if ((sample[1]<0) || (sample[1]>255) )
	{ 
		sample[1]=0;
	}
	if ((sample[2]<0) || (sample[2]>255) )
	{ 
		sample[2]=0;
	}*/



	colorBytes[ 0 ] = sample[ 0 ];
	colorBytes[ 1 ] = sample[ 1 ];
	colorBytes[ 2 ] = sample[ 2 ];
}



/* -------------------------------------------------------------------------------

this section deals with phong shading (normal interpolation across brush faces)

------------------------------------------------------------------------------- */

/*
SmoothNormals()
smooths together coincident vertex normals across the bsp
*/

#define MAX_SAMPLES				256
#define THETA_EPSILON			0.000001
#define EQUAL_NORMAL_EPSILON	0.01

void SmoothNormals( void )
{
	int					i, j, k, f, cs, numVerts, numVotes, fOld, start;
	float				shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle;
	bspDrawSurface_t	*ds;
	shaderInfo_t		*si;
	float				*shadeAngles;
	byte				*smoothed;
	vec3_t				average, diff;
	int					indexes[ MAX_SAMPLES ];
	vec3_t				votes[ MAX_SAMPLES ];
	
	
	/* allocate shade angle table */
	shadeAngles = safe_malloc( numBSPDrawVerts * sizeof( float ) );
	memset( shadeAngles, 0, numBSPDrawVerts * sizeof( float ) );
	
	/* allocate smoothed table */
	cs = (numBSPDrawVerts / 8) + 1;
	smoothed = safe_malloc( cs );
	memset( smoothed, 0, cs );
	
	/* set default shade angle */
	defaultShadeAngle = DEG2RAD( shadeAngleDegrees );
	maxShadeAngle = 0;
	
	/* run through every surface and flag verts belonging to non-lightmapped surfaces
	   and set per-vertex smoothing angle */
	for( i = 0; i < numBSPDrawSurfaces; i++ )
	{
		/* get drawsurf */
		ds = &bspDrawSurfaces[ i ];
		
		/* get shader for shade angle */
		si = surfaceInfos[ i ].si;
		if( si->shadeAngleDegrees )
			shadeAngle = DEG2RAD( si->shadeAngleDegrees );
		else
			shadeAngle = defaultShadeAngle;
		if( shadeAngle > maxShadeAngle )
			maxShadeAngle = shadeAngle;
		
		/* flag its verts */
		for( j = 0; j < ds->numVerts; j++ )
		{
			f = ds->firstVert + j;
			shadeAngles[ f ] = shadeAngle;
			if( ds->surfaceType == MST_TRIANGLE_SOUP )
				smoothed[ f >> 3 ] |= (1 << (f & 7));
		}
		
		/* ydnar: optional force-to-trisoup */
		if( trisoup && ds->surfaceType == MST_PLANAR )
		{
			ds->surfaceType = MST_TRIANGLE_SOUP;
			ds->lightmapNum[ 0 ] = -3;
		}
	}
	
	/* bail if no surfaces have a shade angle */
	if( maxShadeAngle == 0 )
	{
		free( shadeAngles );
		free( smoothed );
		return;
	}
	
	/* init pacifier */
	fOld = -1;
	start = I_FloatTime();
	
	/* go through the list of vertexes */
	for( i = 0; i < numBSPDrawVerts; i++ )
	{
		/* print pacifier */
		f = 10 * i / numBSPDrawVerts;
		if( f != fOld )
		{
			fOld = f;
			Sys_Printf( "%i...", f );
		}
		
		/* already smoothed? */
		if( smoothed[ i >> 3 ] & (1 << (i & 7)) )
			continue;
		
		/* clear */
		VectorClear( average );
		numVerts = 0;
		numVotes = 0;
		
		/* build a table of coincident vertexes */
		for( j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++ )
		{
			/* already smoothed? */
			if( smoothed[ j >> 3 ] & (1 << (j & 7)) )
				continue;
			
			/* test vertexes */
			if( VectorCompare( yDrawVerts[ i ].xyz, yDrawVerts[ j ].xyz ) == qfalse )
				continue;
			
			/* use smallest shade angle */
			shadeAngle = (shadeAngles[ i ] < shadeAngles[ j ] ? shadeAngles[ i ] : shadeAngles[ j ]);
			
			/* check shade angle */
			dot = DotProduct( bspDrawVerts[ i ].normal, bspDrawVerts[ j ].normal );
			if( dot > 1.0 )
				dot = 1.0;
			else if( dot < -1.0 )
				dot = -1.0;
			testAngle = acos( dot ) + THETA_EPSILON;
			if( testAngle >= shadeAngle )
			{
				//Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
				continue;
			}
			//Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) );
			
			/* add to the list */
			indexes[ numVerts++ ] = j;
			
			/* flag vertex */
			smoothed[ j >> 3 ] |= (1 << (j & 7));
			
			/* see if this normal has already been voted */
			for( k = 0; k < numVotes; k++ )
			{
				VectorSubtract( bspDrawVerts[ j ].normal, votes[ k ], diff );
				if( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON &&
					fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON &&
					fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON )
					break;
			}
			
			/* add a new vote? */
			if( k == numVotes && numVotes < MAX_SAMPLES )
			{
				VectorAdd( average, bspDrawVerts[ j ].normal, average );
				VectorCopy( bspDrawVerts[ j ].normal, votes[ numVotes ] );
				numVotes++;
			}
		}
		
		/* don't average for less than 2 verts */
		if( numVerts < 2 )
			continue;
		
		/* average normal */
		if( VectorNormalize( average, average ) > 0 )
		{
			/* smooth */
			for( j = 0; j < numVerts; j++ )
				VectorCopy( average, yDrawVerts[ indexes[ j ] ].normal );
		}
	}
	
	/* free the tables */
	free( shadeAngles );
	free( smoothed );
	
	/* print time */
	Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) );
}



/* -------------------------------------------------------------------------------

this section deals with phong shaded lightmap tracing

------------------------------------------------------------------------------- */

/* 9th rewrite (recursive subdivision of a lightmap triangle) */

/*
CalcTangentVectors()
calculates the st tangent vectors for normalmapping
*/


static qboolean CalcTangentVectors( int numVerts, bspDrawVert_t **dv, vec3_t *stv, vec3_t *ttv )
{
	int			i;
	float		bb, s, t;
	vec3_t		bary;
	vec3_t		norm;

	vec3_t  face_bi_tangent;
	vec3_t face_tangent;
	vec3_t facenorm;
	
	//calculate face tangents
	{
		vec3_t d1;
		vec3_t d2;
		vec3_t outA,outB;
		
		float deltaU_0;
		float deltaU_1;
		float deltaV_0;
		float deltaV_1;

		int a,b,c;
		a=0;
		b=1;
		c=2;

		VectorSubtract( dv[b]->xyz,dv[a]->xyz,d1);
		VectorSubtract( dv[c]->xyz,dv[a]->xyz,d2);

		//calculate the face normal
		CrossProduct(d1,d2,facenorm);
		VectorNormalize(facenorm,facenorm);


		deltaU_0 = dv[b]->st[0] - dv[a]->st[0];
		deltaU_1 = dv[c]->st[0] - dv[a]->st[0];

		deltaV_0 = dv[b]->st[1] - dv[a]->st[1];
		deltaV_1 = dv[c]->st[1] - dv[a]->st[1];
		
		//face_tangent =  normalize( delta_V_1 * side_0 - delta_V_0 * side_1 );
		//face_bi_tangent =  normalize( delta_U_1 * side_0 - delta_U_0 * side_1 );

		
		face_tangent[0] =( deltaV_1 * d1[0] - deltaV_0 * d2[0]);
        face_tangent[1] =( deltaV_1 * d1[1] - deltaV_0 * d2[1]);
        face_tangent[2] =( deltaV_1 * d1[2] - deltaV_0 * d2[2]);

        face_bi_tangent[0] =( deltaU_1 * d1[0] - deltaU_0 * d2[0]);
        face_bi_tangent[1] =( deltaU_1 * d1[1] - deltaU_0 * d2[1]);
        face_bi_tangent[2] =( deltaU_1 * d1[2] - deltaU_0 * d2[2]);


		VectorNormalize(face_tangent,face_tangent);
		VectorNormalize(face_bi_tangent,face_bi_tangent);


		//check for opposing tangents
		CrossProduct(face_tangent,face_bi_tangent,norm);
		if (DotProduct(norm,facenorm)<0) 
		{
			//flip
			face_bi_tangent[0]=-face_bi_tangent[0];
			face_bi_tangent[1]=-face_bi_tangent[1];
			face_bi_tangent[2]=-face_bi_tangent[2];
		}

	}
	

	/* do each vertex */
	for( i = 0; i < numVerts; i++ )
	{
		VectorCopy(face_tangent,stv[i]);
		VectorCopy(face_bi_tangent,ttv[i]);
	//	VectorCopy(facenorm,dv[i]->normal);
	}
	
	/* return to caller */
	return qtrue;
}




/*
PerturbNormal()
perterbs the normal by the shader's normalmap in tangent space
*/
	//basis vectors.. these are our 3 sample directions in tangent space
//const vec3_t basis[4]={ { 0.816497,         0, 0.577350}, {-0.408248, -0.707107, 0.577350} ,{-0.408248,  0.707107, 0.577350} , {0.0, 0.0, 1.0 } };
const vec3_t basis[4]={ { 1.0,         0, 0}, {-0.5, -0.86, 0.0} ,{-0.5,  0.86, 0.0} , {0.0, 0.0, 1.0 } };


static void RadbumpNormal( vec3_t dvNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t pNormal )
{
	if (radbump==qfalse) return;

	
		/* scale tangent vectors and add to original normal */
		VectorMA( dvNormal, basis[radbumppass ][0], stv[ 0 ], pNormal );
		VectorMA( pNormal, basis[ radbumppass ][1], ttv[ 0 ], pNormal );
		VectorMA( pNormal, basis[ radbumppass ][2], dvNormal, pNormal );
		
		/* renormalize and return */
		VectorNormalize( pNormal, pNormal );
	
}

static void PerturbNormal( bspDrawVert_t *dv, shaderInfo_t *si, vec3_t pNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] )
{
	int			i;
	vec4_t		bump;
	
	
	/* passthrough */
	VectorCopy( dv->normal, pNormal );
	
	
	RadbumpNormal(dv->normal,stv,ttv,pNormal);

	if (si->normalImage==NULL) return; 

	/* sample normalmap */
	if( RadSampleImage( si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump ) == qfalse )
		return;
	
	/* remap sampled normal from [0,255] to [-1,-1] */
	for( i = 0; i < 3; i++ )
		bump[ i ] = (bump[ i ] - 127.0f) * (1.0f / 127.5f);
	
	/* scale tangent vectors and add to original normal */
	VectorMA( dv->normal, bump[ 0 ], stv[ 0 ], pNormal );
	VectorMA( pNormal, bump[ 1 ], ttv[ 0 ], pNormal );
	VectorMA( pNormal, bump[ 2 ], dv->normal, pNormal );
	
	/* renormalize and return */
	VectorNormalize( pNormal, pNormal );
}



/*
MapSingleLuxel()
maps a luxel for triangle bv at
*/

#define NUDGE			0.5f
#define BOGUS_NUDGE		-99999.0f

static int MapSingleLuxel( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv, vec4_t plane, float pass, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] )
{
	int				i, x, y, numClusters, *clusters, pointCluster, *cluster;
	float			*luxel, *origin, *normal, d, lightmapSampleOffset;
	shaderInfo_t	*si;
	vec3_t			pNormal;
	vec3_t			vecs[ 3 ];
	vec3_t			nudged;
	vec3_t			cverts[ 3 ];
	vec3_t			temp;
	vec4_t			sideplane, hostplane;
	vec3_t			origintwo;
	int				j, next;
	float			e;
	float			*nudge;
	static float	nudges[][ 2 ] =
					{
						//%{ 0, 0 },		/* try center first */
						{ -NUDGE, 0 },		/* left */
						{ NUDGE, 0 },		/* right */
						{ 0, NUDGE },		/* up */
						{ 0, -NUDGE },		/* down */
						{ -NUDGE, NUDGE },	/* left/up */
						{ NUDGE, -NUDGE },	/* right/down */
						{ NUDGE, NUDGE },	/* right/up */
						{ -NUDGE, -NUDGE },	/* left/down */
						{ BOGUS_NUDGE, BOGUS_NUDGE }
					};
	
	
	/* find luxel xy coords (fixme: subtract 0.5?) */
	x = dv->lightmap[ 0 ][ 0 ];
	y = dv->lightmap[ 0 ][ 1 ];
	if( x < 0 )
		x = 0;
	else if( x >= lm->sw )
		x = lm->sw - 1;
	if( y < 0 )
		y = 0;
	else if( y >= lm->sh )
		y = lm->sh - 1;
	
	/* set shader and cluster list */
	if( info != NULL )
	{
		si = info->si;
		numClusters = info->numSurfaceClusters;
		clusters = &surfaceClusters[ info->firstSurfaceCluster ];
	}
	else
	{
		si = NULL;
		numClusters = 0;
		clusters = NULL;
	}
	
	/* get luxel, origin, cluster, and normal */
	luxel = SUPER_LUXEL( 0, x, y );
	origin = SUPER_ORIGIN( x, y );
	normal = SUPER_NORMAL( x, y );
	cluster = SUPER_CLUSTER( x, y );
	
	/* don't attempt to remap occluded luxels for planar surfaces */
	if( (*cluster) == CLUSTER_OCCLUDED && lm->plane != NULL )
		return (*cluster);
	
	/* only average the normal for premapped luxels */
	else if( (*cluster) >= 0 )
	{
		/* do bumpmap calculations */
		if( stv != NULL )
			PerturbNormal( dv, si, pNormal, stv, ttv );
		else
			VectorCopy( dv->normal, pNormal );
		
		/* add the additional normal data */
		VectorAdd( normal, pNormal, normal );
		luxel[ 3 ] += 1.0f;
		return (*cluster);
	}
	
	/* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */
	
	/* get origin */
	
	/* axial lightmap projection */
	if( lm->vecs != NULL )
	{
		/* calculate an origin for the sample from the lightmap vectors */
		VectorCopy( lm->origin, origin );
		for( i = 0; i < 3; i++ )
		{
			/* add unless it's the axis, which is taken care of later */
			if( i == lm->axisNum )
				continue;
			origin[ i ] += (x * lm->vecs[ 0 ][ i ]) + (y * lm->vecs[ 1 ][ i ]);
		}
		
		/* project the origin onto the plane */
		d = DotProduct( origin, plane ) - plane[ 3 ];
		d /= plane[ lm->axisNum ];
		origin[ lm->axisNum ] -= d;
	}
	
	/* non axial lightmap projection (explicit xyz) */
	else
		VectorCopy( dv->xyz, origin );

	//////////////////////
	//27's test to make sure samples stay within the triangle boundaries
	//1) Test the sample origin to see if it lays on the wrong side of any edge (x/y)
	//2) if it does, nudge it onto the correct side.

	if (worldverts!=NULL )
	{
		for (j=0;j<3;j++)
		{
			VectorCopy(worldverts[j],cverts[j]);    
		}
		PlaneFromPoints(hostplane,cverts[0],cverts[1],cverts[2]);

		if (noboundstest==qfalse)
		{
			for (j=0;j<3;j++)
			{
				for (i=0;i<3;i++)
				{
					//build plane using 2 edges and a normal
					next=(i+1)%3;

					VectorCopy(cverts[next],temp);
					VectorAdd(temp,hostplane,temp);
					PlaneFromPoints(sideplane,cverts[i],cverts[ next ], temp);

					//planetest sample point  
					e=DotProduct(origin,sideplane);
					e=e-sideplane[3];
					if (e>0)
					{
						//we're bad.
						//VectorClear(origin);
						//Move the sample point back inside triangle bounds
						origin[0]-=sideplane[0]*(e+1);
						origin[1]-=sideplane[1]*(e+1);
						origin[2]-=sideplane[2]*(e+1);
	#ifdef DEBUG_27_1
						VectorClear(origin);
	#endif 
					}
				}
			}
		}
	}

	////////////////////////
	
	/* planar surfaces have precalculated lightmap vectors for nudging */
	if( lm->plane != NULL )
	{
		VectorCopy( lm->vecs[ 0 ], vecs[ 0 ] );
		VectorCopy( lm->vecs[ 1 ], vecs[ 1 ] );
		VectorCopy( lm->plane, vecs[ 2 ] );
	}
	
	/* non-planar surfaces must calculate them */
	else
	{
		if( plane != NULL )
			VectorCopy( plane, vecs[ 2 ] );
		else
			VectorCopy( dv->normal, vecs[ 2 ] );
		MakeNormalVectors( vecs[ 2 ], vecs[ 0 ], vecs[ 1 ] );
	}
	
	/* push the origin off the surface a bit */
	if( si != NULL )
		lightmapSampleOffset = si->lightmapSampleOffset;
	else
		lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET;
	if( lm->axisNum < 0 )
		VectorMA( origin, lightmapSampleOffset, vecs[ 2 ], origin );
	else if( vecs[ 2 ][ lm->axisNum ] < 0.0f )
		origin[ lm->axisNum ] -= lightmapSampleOffset;
	else
		origin[ lm->axisNum ] += lightmapSampleOffset;
	
	VectorCopy(origin,origintwo);
	origintwo[0]+=vecs[2][0];
	origintwo[1]+=vecs[2][1];
	origintwo[2]+=vecs[2][2];
	
	/* get cluster */
	pointCluster = ClusterForPointExtFilter( origintwo, LUXEL_EPSILON, numClusters, clusters );
	
	/* another retarded hack, storing nudge count in luxel[ 1 ] */
	luxel[ 1 ] = 0.0f;	
	
	/* point in solid? (except in dark mode) */
	if( pointCluster < 0 && dark == qfalse )
	{
		/* nudge the the location around */
		nudge = nudges[ 0 ];
		while( nudge[ 0 ] > BOGUS_NUDGE && pointCluster < 0 )
		{
			/* nudge the vector around a bit */
			for( i = 0; i < 3; i++ )
			{
				/* set nudged point*/
				nudged[ i ] = origintwo[ i ] + (nudge[ 0 ] * vecs[ 0 ][ i ]) + (nudge[ 1 ] * vecs[ 1 ][ i ]);
			}
			nudge += 2;
			
			/* get pvs cluster */
			pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //% + 0.625 );
			//if( pointCluster >= 0 )	
   			//	VectorCopy( nudged, origin );
			luxel[ 1 ] += 1.0f;
		}
	}
	
	/* as a last resort, if still in solid, try drawvert origin offset by normal (except in dark mode) */
	if( pointCluster < 0 && si != NULL && dark == qfalse )
	{
		VectorMA( dv->xyz, lightmapSampleOffset, dv->normal, nudged );
		pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters );
		//if( pointCluster >= 0 )
		//	VectorCopy( nudged, origin );
		luxel[ 1 ] += 1.0f;
	}
	
	/* valid? */
	if( pointCluster < 0 )
	{
		(*cluster) = CLUSTER_OCCLUDED;
		VectorClear( origin );
		VectorClear( normal );
		numLuxelsOccluded++;
		return (*cluster);
	}
	
	/* debug code */
	//%	Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] );
	
	/* do bumpmap calculations */
	if( stv )
		PerturbNormal( dv, si, pNormal, stv, ttv );
	else
		VectorCopy( dv->normal, pNormal );
	
	/* store the cluster and normal */
	(*cluster) = pointCluster;
	VectorCopy( pNormal, normal );
	
	/* store explicit mapping pass and implicit mapping pass */
	luxel[ 0 ] = pass;
	luxel[ 3 ] = 1.0f;
	
	/* add to count */
	numLuxelsMapped++;
	
	/* return ok */
	return (*cluster);
}



/*
MapTriangle_r()
recursively subdivides a triangle until its edges are shorter
than the distance between two luxels (thanks jc :)
*/

static void MapTriangle_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], vec4_t plane, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] )
{
	bspDrawVert_t	mid, *dv2[ 3 ];
	int				max;
	
	
	/* map the vertexes */
	#if 0
	MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv );
	MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv );
	MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv );
	#endif
	
	/* subdivide calc */
	{
		int			i;
		float		*a, *b, dx, dy, dist, maxDist;
		
		
		/* find the longest edge and split it */
		max = -1;
		maxDist = 0;
		for( i = 0; i < 3; i++ )
		{
			/* get verts */
			a = dv[ i ]->lightmap[ 0 ];
			b = dv[ (i + 1) % 3 ]->lightmap[ 0 ];
			
			/* get dists */
			dx = a[ 0 ] - b[ 0 ];
			dy = a[ 1 ] - b[ 1 ];
			dist = (dx * dx) + (dy * dy);	//% sqrt( (dx * dx) + (dy * dy) );
			
			/* longer? */
			if( dist > maxDist )
			{
				maxDist = dist;
				max = i;
			}
		}
		
		/* try to early out */
		if( max < 0 || maxDist <= subdivideThreshold )	/* ydnar: was i < 0 instead of max < 0 (?) */
			return;
	}
	
	/* split the longest edge and map it */
	LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid );
	MapSingleLuxel( lm, info, &mid, plane, 1, stv, ttv, worldverts );
	
	/* push the point up a little bit to account for fp creep (fixme: revisit this) */
	//%	VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz );
	
	/* recurse to first triangle */
	VectorCopy( dv, dv2 );
	dv2[ max ] = &mid;
	MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts );
	
	/* recurse to second triangle */
	VectorCopy( dv, dv2 );
	dv2[ (max + 1) % 3 ] = &mid;
	MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts );
}



/*
MapTriangle()
seed function for MapTriangle_r()
requires a cw ordered triangle
*/

static qboolean MapTriangle( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], qboolean mapNonAxial )
{
	int				i;
	vec4_t			plane;
	vec3_t			*stv, *ttv, stvStatic[ 3 ], ttvStatic[ 3 ];
	vec3_t			worldverts[ 3 ];
	
	
	/* get plane if possible */
	if( lm->plane != NULL )
	{
		VectorCopy( lm->plane, plane );
		plane[ 3 ] = lm->plane[ 3 ];
	}
	
	/* otherwise make one from the points */
	else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse )
		return qfalse;
	
	/* check to see if we need to calculate texture->world tangent vectors */
	if( (info->si->normalImage != NULL || radbump) && CalcTangentVectors( 3, dv, stvStatic, ttvStatic )  )
	{
		stv = stvStatic;
		ttv = ttvStatic;
	}
	else
	{
		stv = NULL;
		ttv = NULL;
	}
	
	VectorCopy( dv[ 0 ]->xyz, worldverts[ 0 ] );
	VectorCopy( dv[ 1 ]->xyz, worldverts[ 1 ] );
	VectorCopy( dv[ 2 ]->xyz, worldverts[ 2 ] );
	
	/* map the vertexes */
	MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, worldverts );
	MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, worldverts );
	MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, worldverts );
	
	/* 2002-11-20: prefer axial triangle edges */
	if( mapNonAxial )
	{
		/* subdivide the triangle */
		MapTriangle_r( lm, info, dv, plane, stv, ttv, worldverts );
		return qtrue;
	}
	
	for( i = 0; i < 3; i++ )
	{
		float			*a, *b;
		bspDrawVert_t	*dv2[ 3 ];
		
		
		/* get verts */
		a = dv[ i ]->lightmap[ 0 ];
		b = dv[ (i + 1) % 3 ]->lightmap[ 0 ];
		
		/* make degenerate triangles for mapping edges */
		if( fabs( a[ 0 ] - b[ 0 ] ) < 0.01f || fabs( a[ 1 ] - b[ 1 ] ) < 0.01f )
		{
			dv2[ 0 ] = dv[ i ];
			dv2[ 1 ] = dv[ (i + 1) % 3 ];
			dv2[ 2 ] = dv[ (i + 1) % 3 ];
			
			/* map the degenerate triangle */
			MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts );
		}
	}
	
	return qtrue;
}



/*
MapQuad_r()
recursively subdivides a quad until its edges are shorter
than the distance between two luxels
*/

static void MapQuad_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ], vec4_t plane, vec3_t stv[ 4 ], vec3_t ttv[ 4 ] )
{
	bspDrawVert_t	mid[ 2 ], *dv2[ 4 ];
	int				max;
	
	
	/* subdivide calc */
	{
		int			i;
		float		*a, *b, dx, dy, dist, maxDist;
		
		
		/* find the longest edge and split it */
		max = -1;
		maxDist = 0;
		for( i = 0; i < 4; i++ )
		{
			/* get verts */
			a = dv[ i ]->lightmap[ 0 ];
			b = dv[ (i + 1) % 4 ]->lightmap[ 0 ];
			
			/* get dists */
			dx = a[ 0 ] - b[ 0 ];
			dy = a[ 1 ] - b[ 1 ];
			dist = (dx * dx) + (dy * dy);	//% sqrt( (dx * dx) + (dy * dy) );
			
			/* longer? */
			if( dist > maxDist )
			{
				maxDist = dist;
				max = i;
			}
		}
		
		/* try to early out */
		if( max < 0 || maxDist <= subdivideThreshold )
			return;
	}
	
	/* we only care about even/odd edges */
	max &= 1;
	
	/* split the longest edges */
	LerpDrawVert( dv[ max ], dv[ (max + 1) % 4 ], &mid[ 0 ] );
	LerpDrawVert( dv[ max + 2 ], dv[ (max + 3) % 4 ], &mid[ 1 ] );
	
	/* map the vertexes */
	MapSingleLuxel( lm, info, &mid[ 0 ], plane, 1, stv, ttv, NULL );
	MapSingleLuxel( lm, info, &mid[ 1 ], plane, 1, stv, ttv, NULL );
	
	/* 0 and 2 */
	if( max == 0 )
	{
		/* recurse to first quad */
		dv2[ 0 ] = dv[ 0 ];
		dv2[ 1 ] = &mid[ 0 ];
		dv2[ 2 ] = &mid[ 1 ];
		dv2[ 3 ] = dv[ 3 ];
		MapQuad_r( lm, info, dv2, plane, stv, ttv );
		
		/* recurse to second quad */
		dv2[ 0 ] = &mid[ 0 ];
		dv2[ 1 ] = dv[ 1 ];
		dv2[ 2 ] = dv[ 2 ];
		dv2[ 3 ] = &mid[ 1 ];
		MapQuad_r( lm, info, dv2, plane, stv, ttv );
	}
	
	/* 1 and 3 */
	else
	{
		/* recurse to first quad */
		dv2[ 0 ] = dv[ 0 ];
		dv2[ 1 ] = dv[ 1 ];
		dv2[ 2 ] = &mid[ 0 ];
		dv2[ 3 ] = &mid[ 1 ];
		MapQuad_r( lm, info, dv2, plane, stv, ttv );
		
		/* recurse to second quad */
		dv2[ 0 ] = &mid[ 1 ];
		dv2[ 1 ] = &mid[ 0 ];
		dv2[ 2 ] = dv[ 2 ];
		dv2[ 3 ] = dv[ 3 ];
		MapQuad_r( lm, info, dv2, plane, stv, ttv );
	}
}



/*
MapQuad()
seed function for MapQuad_r()
requires a cw ordered triangle quad
*/

#define QUAD_PLANAR_EPSILON		0.5f

static qboolean MapQuad( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ] )
{
	float			dist;
	vec4_t			plane;
	vec3_t			*stv, *ttv, stvStatic[ 4 ], ttvStatic[ 4 ];
	
	
	/* get plane if possible */
	if( lm->plane != NULL )
	{
		VectorCopy( lm->plane, plane );
		plane[ 3 ] = lm->plane[ 3 ];
	}
	
	/* otherwise make one from the points */
	else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse )
		return qfalse;
	
	/* 4th point must fall on the plane */
	dist = DotProduct( plane, dv[ 3 ]->xyz ) - plane[ 3 ];
	if( fabs( dist ) > QUAD_PLANAR_EPSILON )
		return qfalse;
	
	/* check to see if we need to calculate texture->world tangent vectors */
	if( (info->si->normalImage != NULL || radbump )&& CalcTangentVectors( 4, dv, stvStatic, ttvStatic )  )
	{
		stv = stvStatic;
		ttv = ttvStatic;
	}
	else
	{
		stv = NULL;
		ttv = NULL;
	}
	
	/* map the vertexes */
	MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, NULL );
	MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, NULL );
	MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, NULL );
	MapSingleLuxel( lm, info, dv[ 3 ], plane, 1, stv, ttv, NULL );
	
	/* subdivide the quad */
	MapQuad_r( lm, info, dv, plane, stv, ttv );
	return qtrue;
}



/*
MapRawLightmap()
maps the locations, normals, and pvs clusters for a raw lightmap
*/

#define VectorDivide( in, d, out )	VectorScale( in, (1.0f / (d)), out )	//%	(out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d)

void MapRawLightmap( int rawLightmapNum )
{
	int					n, num, i, x, y, sx, sy, pw[ 5 ], r, *cluster, mapNonAxial;
	float				*luxel, *origin, *normal, samples, radius, pass;
	rawLightmap_t		*lm;
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	mesh_t				src, *subdivided, *mesh;
	bspDrawVert_t		*verts, *dv[ 4 ], fake;
	
	
	/* bail if this number exceeds the number of raw lightmaps */
	if( rawLightmapNum >= numRawLightmaps )
		return;
	
	/* get lightmap */
	lm = &rawLightmaps[ rawLightmapNum ];
	
	/* -----------------------------------------------------------------
	   map referenced surfaces onto the raw lightmap
	   ----------------------------------------------------------------- */
	
	/* walk the list of surfaces on this raw lightmap */
	for( n = 0; n < lm->numLightSurfaces; n++ )
	{
		/* with > 1 surface per raw lightmap, clear occluded */
		if( n > 0 )
		{
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster */
					cluster = SUPER_CLUSTER( x, y );
					if( *cluster < 0 )
						*cluster = CLUSTER_UNMAPPED;
				}
			}
		}
		
		/* get surface */
		num = lightSurfaces[ lm->firstLightSurface + n ];
		ds = &bspDrawSurfaces[ num ];
		info = &surfaceInfos[ num ];
		
		/* bail if no lightmap to calculate */
		if( info->lm != lm )
		{
			Sys_Printf( "!" );
			continue;
		}
		
		/* map the surface onto the lightmap origin/cluster/normal buffers */
		switch( ds->surfaceType )
		{
			case MST_PLANAR:
				/* get verts */
				verts = yDrawVerts + ds->firstVert;
				
				/* map the triangles */
				for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ )
				{
					for( i = 0; i < ds->numIndexes; i += 3 )
					{
						dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ];
						dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ];
						dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ];
						MapTriangle( lm, info, dv, mapNonAxial );
					}
				}
				break;
			
			case MST_PATCH:
				/* make a mesh from the drawsurf */ 
				src.width = ds->patchWidth;
				src.height = ds->patchHeight;
				src.verts = &yDrawVerts[ ds->firstVert ];
				//%	subdivided = SubdivideMesh( src, 8, 512 );
				subdivided = SubdivideMesh2( src, info->patchIterations );
				
				/* fit it to the curve and remove colinear verts on rows/columns */
				PutMeshOnCurve( *subdivided );
				mesh = RemoveLinearMeshColumnsRows( subdivided );
				FreeMesh( subdivided );
				
				/* get verts */
				verts = mesh->verts;
				
				/* debug code */
				#if 0
					if( lm->plane )
					{
						Sys_Printf( "Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n",
							lm->plane[ 0 ], lm->plane[ 1 ], lm->plane[ 2 ],
							lm->vecs[ 0 ][ 0 ], lm->vecs[ 0 ][ 1 ], lm->vecs[ 0 ][ 2 ],
							lm->vecs[ 1 ][ 0 ], lm->vecs[ 1 ][ 1 ], lm->vecs[ 1 ][ 2 ] );
					}
				#endif
				
				/* map the mesh quads */
				#if 0

				for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ )
				{
					for( y = 0; y < (mesh->height - 1); y++ )
					{
						for( x = 0; x < (mesh->width - 1); x++ )
						{
							/* set indexes */
							pw[ 0 ] = x + (y * mesh->width);
							pw[ 1 ] = x + ((y + 1) * mesh->width);
							pw[ 2 ] = x + 1 + ((y + 1) * mesh->width);
							pw[ 3 ] = x + 1 + (y * mesh->width);
							pw[ 4 ] = x + (y * mesh->width);	/* same as pw[ 0 ] */
							
							/* set radix */
							r = (x + y) & 1;
							
							/* get drawverts and map first triangle */
							dv[ 0 ] = &verts[ pw[ r + 0 ] ];
							dv[ 1 ] = &verts[ pw[ r + 1 ] ];
							dv[ 2 ] = &verts[ pw[ r + 2 ] ];
							MapTriangle( lm, info, dv, mapNonAxial );
							
							/* get drawverts and map second triangle */
							dv[ 0 ] = &verts[ pw[ r + 0 ] ];
							dv[ 1 ] = &verts[ pw[ r + 2 ] ];
							dv[ 2 ] = &verts[ pw[ r + 3 ] ];
							MapTriangle( lm, info, dv, mapNonAxial );
						}
					}
				}
				
				#else
				
				for( y = 0; y < (mesh->height - 1); y++ )
				{
					for( x = 0; x < (mesh->width - 1); x++ )
					{
						/* set indexes */
						pw[ 0 ] = x + (y * mesh->width);
						pw[ 1 ] = x + ((y + 1) * mesh->width);
						pw[ 2 ] = x + 1 + ((y + 1) * mesh->width);
						pw[ 3 ] = x + 1 + (y * mesh->width);
						pw[ 4 ] = pw[ 0 ];
						
						/* set radix */
						r = (x + y) & 1;
						
						/* attempt to map quad first */
						dv[ 0 ] = &verts[ pw[ r + 0 ] ];
						dv[ 1 ] = &verts[ pw[ r + 1 ] ];
						dv[ 2 ] = &verts[ pw[ r + 2 ] ];
						dv[ 3 ] = &verts[ pw[ r + 3 ] ];
						if( MapQuad( lm, info, dv ) )
							continue;
						
						/* get drawverts and map first triangle */
						MapTriangle( lm, info, dv, mapNonAxial );
						
						/* get drawverts and map second triangle */
						dv[ 1 ] = &verts[ pw[ r + 2 ] ];
						dv[ 2 ] = &verts[ pw[ r + 3 ] ];
						MapTriangle( lm, info, dv, mapNonAxial );
					}
				}
				
				#endif
				
				/* free the mesh */
				FreeMesh( mesh );
				break;
			
			default:
				break;
		}
	}
	
	/* -----------------------------------------------------------------
	   average and clean up luxel normals
	   ----------------------------------------------------------------- */
	
	/* walk the luxels */
	for( y = 0; y < lm->sh; y++ )
	{
		for( x = 0; x < lm->sw; x++ )
		{
			/* get luxel */
			luxel = SUPER_LUXEL( 0, x, y );
			normal = SUPER_NORMAL( x, y );
			cluster = SUPER_CLUSTER( x, y );

			/* only look at mapped luxels */
			if( *cluster < 0 )
				continue;
			
			/* the normal data could be the sum of multiple samples */
			if( luxel[ 3 ] > 1.0f )
				VectorNormalize( normal, normal );
			
			/* mark this luxel as having only one normal */
			luxel[ 3 ] = 1.0f;
		}
	}
	
	/* non-planar surfaces stop here */
	if( lm->plane == NULL )
		return;
	
	/* -----------------------------------------------------------------
	   map occluded or unuxed luxels
	   ----------------------------------------------------------------- */
	
	/* walk the luxels */
	radius = floor( superSample / 2 );
	radius = radius > 0 ? radius : 1.0f;
	radius += 1.0f;
	for( pass = 2.0f; pass <= radius; pass += 1.0f )
	{
		for( y = 0; y < lm->sh; y++ )
		{
			for( x = 0; x < lm->sw; x++ )
			{
				/* get luxel */
				luxel = SUPER_LUXEL( 0, x, y );
				normal = SUPER_NORMAL( x, y );
				cluster = SUPER_CLUSTER( x, y );
				
				/* only look at unmapped luxels */
				if( *cluster != CLUSTER_UNMAPPED )
					continue;
				
				/* divine a normal and origin from neighboring luxels */
				VectorClear( fake.xyz );
				VectorClear( fake.normal );
				fake.lightmap[ 0 ][ 0 ] = x;	//% 0.0001 + x;
				fake.lightmap[ 0 ][ 1 ] = y;	//% 0.0001 + y;
				samples = 0.0f;
				for( sy = (y - 1); sy <= (y + 1); sy++ )
				{
					if( sy < 0 || sy >= lm->sh )
						continue;
					
					for( sx = (x - 1); sx <= (x + 1); sx++ )
					{
						if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) )
							continue;
						
						/* get neighboring luxel */
						luxel = SUPER_LUXEL( 0, sx, sy );
						origin = SUPER_ORIGIN( sx, sy );
						normal = SUPER_NORMAL( sx, sy );
						cluster = SUPER_CLUSTER( sx, sy );
						
						/* only consider luxels mapped in previous passes */
						if( *cluster < 0 || luxel[ 0 ] >= pass )
							continue;
						
						/* add its distinctiveness to our own */
						VectorAdd( fake.xyz, origin, fake.xyz );
						VectorAdd( fake.normal, normal, fake.normal );
						samples += luxel[ 3 ];
					}
				}
				
				/* any samples? */
				if( samples == 0.0f )
					continue;
				
				/* average */
				VectorDivide( fake.xyz, samples, fake.xyz );
				//%	VectorDivide( fake.normal, samples, fake.normal );
				if( VectorNormalize( fake.normal, fake.normal ) == 0.0f )
					continue;
				
				/* map the fake vert */
				MapSingleLuxel( lm, NULL, &fake, lm->plane, pass, NULL, NULL, NULL );
			}
		}
	}
	
	/* -----------------------------------------------------------------
	   average and clean up luxel normals
	   ----------------------------------------------------------------- */
	
	/* walk the luxels */
	for( y = 0; y < lm->sh; y++ )
	{
		for( x = 0; x < lm->sw; x++ )
		{
			/* get luxel */
			luxel = SUPER_LUXEL( 0, x, y );
			normal = SUPER_NORMAL( x, y );
			cluster = SUPER_CLUSTER( x, y );
			
			/* only look at mapped luxels */
			if( *cluster < 0 )
				continue;
			
			/* the normal data could be the sum of multiple samples */
			if( luxel[ 3 ] > 1.0f )
				VectorNormalize( normal, normal );
			
			/* mark this luxel as having only one normal */
			luxel[ 3 ] = 1.0f;
		}
	}
	
	/* debug code */
	#if 0
		Sys_Printf( "\n" );
		for( y = 0; y < lm->sh; y++ )
		{
			for( x = 0; x < lm->sw; x++ )
			{
				vec3_t	mins, maxs;
				

				cluster = SUPER_CLUSTER( x, y );
				origin = SUPER_ORIGIN( x, y );
				normal = SUPER_NORMAL( x, y );
				luxel = SUPER_LUXEL( x, y );
				
				if( *cluster < 0 )
					continue;
				
				/* check if within the bounding boxes of all surfaces referenced */
				ClearBounds( mins, maxs );
				for( n = 0; n < lm->numLightSurfaces; n++ )
				{
					int TOL;
					info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + n ] ];
					TOL = info->sampleSize + 2;
					AddPointToBounds( info->mins, mins, maxs );
					AddPointToBounds( info->maxs, mins, maxs );
					if( origin[ 0 ] > (info->mins[ 0 ] - TOL) && origin[ 0 ] < (info->maxs[ 0 ] + TOL) &&
						origin[ 1 ] > (info->mins[ 1 ] - TOL) && origin[ 1 ] < (info->maxs[ 1 ] + TOL) &&
						origin[ 2 ] > (info->mins[ 2 ] - TOL) && origin[ 2 ] < (info->maxs[ 2 ] + TOL) )
						break;
				}
				
				/* inside? */
				if( n < lm->numLightSurfaces )
					continue;
				
				/* report bogus origin */
				Sys_Printf( "%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n",
					rawLightmapNum, x, y, *cluster,
					origin[ 0 ], origin[ 1 ], origin[ 2 ],
					mins[ 0 ], mins[ 1 ], mins[ 2 ],
					maxs[ 0 ], maxs[ 1 ], maxs[ 2 ],
					luxel[ 3 ] );
			}
		}
	#endif
}



/*
SetupDirt()
sets up dirtmap (ambient occlusion)
*/

#define DIRT_CONE_ANGLE				88	/* degrees */
#define DIRT_NUM_ANGLE_STEPS		16
#define DIRT_NUM_ELEVATION_STEPS	3
#define	DIRT_NUM_VECTORS			(DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS)

static vec3_t		dirtVectors[ DIRT_NUM_VECTORS ];
static int			numDirtVectors = 0;

void SetupDirt( void )
{
	int		i, j;
	float	angle, elevation, angleStep, elevationStep;
	
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "--- SetupDirt ---\n" );
	
	/* calculate angular steps */
	angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS );
	elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS );
	
	/* iterate angle */
	angle = 0.0f;
	for( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep )
	{
		/* iterate elevation */
		for( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep )
		{
			dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle );
			dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle );
			dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation );
			numDirtVectors++;
		}
	}
	
	/* emit some statistics */
	Sys_FPrintf( SYS_VRB, "%9d dirtmap vectors\n", numDirtVectors );
}


/*
DirtForSample()
calculates dirt value for a given sample
*/

float DirtForSample( trace_t *trace )
{
	int		i;
	float	gatherDirt, outDirt, angle, elevation, ooDepth;
	vec3_t	normal, worldUp, myUp, myRt, temp, direction, displacement;
	
	
	/* dummy check */
	if( !dirty )
		return 1.0f;
	if( trace == NULL || trace->cluster < 0 )
		return 0.0f;
	
	/* setup */
	gatherDirt = 0.0f;
	ooDepth = 1.0f / dirtDepth;
	VectorCopy( trace->normal, normal );
	
	/* check if the normal is aligned to the world-up */
	if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f )
	{
		if( normal[ 2 ] == 1.0f )		
		{
			VectorSet( myRt, 1.0f, 0.0f, 0.0f );
			VectorSet( myUp, 0.0f, 1.0f, 0.0f );
		}
		else if( normal[ 2 ] == -1.0f )
		{
			VectorSet( myRt, -1.0f, 0.0f, 0.0f );
			VectorSet( myUp,  0.0f, 1.0f, 0.0f );
		}
	}
	else
	{
		VectorSet( worldUp, 0.0f, 0.0f, 1.0f );
		CrossProduct( normal, worldUp, myRt );
		VectorNormalize( myRt, myRt );
		CrossProduct( myRt, normal, myUp );
		VectorNormalize( myUp, myUp );
	}
	
	/* 1 = random mode, 0 (well everything else) = non-random mode */
	if( dirtMode == 1 )
	{
		/* iterate */
		for( i = 0; i < numDirtVectors; i++ )
		{
			/* get random vector */
			angle = Random() * DEG2RAD( 360.0f );
			elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE );
			temp[ 0 ] = cos( angle ) * sin( elevation );
			temp[ 1 ] = sin( angle ) * sin( elevation );
			temp[ 2 ] = cos( elevation );
			
			/* transform into tangent space */
			direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ];
			direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ];
			direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ];
			
			/* set endpoint */
			VectorMA( trace->origin, dirtDepth, direction, trace->end );
			SetupTrace( trace );
			
			/* trace */
			TraceLine( trace );
			if( trace->opaque )
			{
				VectorSubtract( trace->hit, trace->origin, displacement );
				gatherDirt += 1.0f - ooDepth * VectorLength( displacement );
			}
		}
	}
	else
	{
		/* iterate through ordered vectors */
		for( i = 0; i < numDirtVectors; i++ )
		{
			/* transform vector into tangent space */
			direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ];
			direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ];
			direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ];
			
			/* set endpoint */
			VectorMA( trace->origin, dirtDepth, direction, trace->end );
			SetupTrace( trace );
			
			/* trace */
			TraceLine( trace );
			if( trace->opaque )
			{
				VectorSubtract( trace->hit, trace->origin, displacement );
				gatherDirt += 1.0f - ooDepth * VectorLength( displacement );
			}
		}
	}
	
	/* direct ray */
	VectorMA( trace->origin, dirtDepth, normal, trace->end );
	SetupTrace( trace );
	
	/* trace */
	TraceLine( trace );
	if( trace->opaque )
	{
		VectorSubtract( trace->hit, trace->origin, displacement );
		gatherDirt += 1.0f - ooDepth * VectorLength( displacement );
	}
	
	/* early out */
	if( gatherDirt <= 0.0f )
		return 1.0f;
	
	/* apply gain (does this even do much? heh) */
	outDirt = pow( gatherDirt / (numDirtVectors + 1), dirtGain );
	if( outDirt > 1.0f )
		outDirt = 1.0f;
	
	/* apply scale */
	outDirt *= dirtScale;
	if( outDirt > 1.0f )
		outDirt = 1.0f;
	
	/* return to sender */
	return 1.0f - outDirt;
}



/*
DirtyRawLightmap()
calculates dirty fraction for each luxel
*/

void DirtyRawLightmap( int rawLightmapNum )
{
	int					i, x, y, sx, sy, *cluster;
	float				*origin, *normal, *dirt, *dirt2, average, samples;
	rawLightmap_t		*lm;
	surfaceInfo_t		*info;
	trace_t				trace;
	
	
	/* bail if this number exceeds the number of raw lightmaps */
	if( rawLightmapNum >= numRawLightmaps )
		return;
	
	/* get lightmap */
	lm = &rawLightmaps[ rawLightmapNum ];
	
	/* setup trace */
	trace.testOcclusion = qtrue;
	trace.forceSunlight = qfalse;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[ lm->firstLightSurface ];
	trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
	trace.testAll = qfalse;
	
	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	trace.twoSided = qfalse;
	for( i = 0; i < trace.numSurfaces; i++ )
	{
		/* get surface */
		info = &surfaceInfos[ trace.surfaces[ i ] ];
		
		/* check twosidedness */
		if( info->si->twoSided )
		{
			trace.twoSided = qtrue;
			break;
		}
	}
	
	/* gather dirt */
	for( y = 0; y < lm->sh; y++ )
	{
		for( x = 0; x < lm->sw; x++ )
		{
			/* get luxel */
			cluster = SUPER_CLUSTER( x, y );
			origin = SUPER_ORIGIN( x, y );
			normal = SUPER_NORMAL( x, y );
			dirt = SUPER_DIRT( x, y );
			
			/* set default dirt */
			*dirt = 0.0f;
			
			/* only look at mapped luxels */
			if( *cluster < 0 )
				continue;
			
			/* copy to trace */
			trace.cluster = *cluster;
			VectorCopy( origin, trace.origin );
			VectorCopy( normal, trace.normal );
			
			/* get dirt */
			*dirt = DirtForSample( &trace );
		}
	}
	
	/* testing no filtering */
	//%	return;
	
	/* filter dirt */
	for( y = 0; y < lm->sh; y++ )
	{
		for( x = 0; x < lm->sw; x++ )
		{
			/* get luxel */
			cluster = SUPER_CLUSTER( x, y );
			dirt = SUPER_DIRT( x, y );
			
			/* filter dirt by adjacency to unmapped luxels */
			average = *dirt;
			samples = 1.0f;
			for( sy = (y - 1); sy <= (y + 1); sy++ )
			{
				if( sy < 0 || sy >= lm->sh )
					continue;
				
				for( sx = (x - 1); sx <= (x + 1); sx++ )
				{
					if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) )
						continue;
					
					/* get neighboring luxel */
					cluster = SUPER_CLUSTER( sx, sy );
					dirt2 = SUPER_DIRT( sx, sy );
					if( *cluster < 0 || *dirt2 <= 0.0f )
						continue;
					
					/* add it */
					average += *dirt2;
					samples += 1.0f;
				}
				
				/* bail */
				if( samples <= 0.0f )
					break;
			}
			
			/* bail */
			if( samples <= 0.0f )
				continue;
			
			/* scale dirt */
			*dirt = average / samples;
		}
	}
}



/*
SubmapRawLuxel()
calculates the pvs cluster, origin, normal of a sub-luxel
*/

static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal )
{
	int			i, *cluster, *cluster2;
	float		*origin, *origin2, *normal;	//%	, *normal2;
	vec3_t		originVecs[ 2 ];			//%	, normalVecs[ 2 ];
	
	
	/* calulate x vector */
	if( (x < (lm->sw - 1) && bx >= 0.0f) || (x == 0 && bx <= 0.0f) )
	{
		cluster = SUPER_CLUSTER( x, y );
		origin = SUPER_ORIGIN( x, y );
		//%	normal = SUPER_NORMAL( x, y );
		cluster2 = SUPER_CLUSTER( x + 1, y );
		origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y );
		//%	normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y );
	}
	else if( (x > 0 && bx <= 0.0f) || (x == (lm->sw - 1) && bx >= 0.0f) )
	{
		cluster = SUPER_CLUSTER( x - 1, y );
		origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y );
		//%	normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y );
		cluster2 = SUPER_CLUSTER( x, y );
		origin2 = SUPER_ORIGIN( x, y );
		//%	normal2 = SUPER_NORMAL( x, y );
	}
	else
		Sys_Printf( "WARNING: Spurious lightmap S vector\n" );
	
	VectorSubtract( origin2, origin, originVecs[ 0 ] );
	//%	VectorSubtract( normal2, normal, normalVecs[ 0 ] );
	
	/* calulate y vector */
	if( (y < (lm->sh - 1) && bx >= 0.0f) || (y == 0 && bx <= 0.0f) )
	{
		cluster = SUPER_CLUSTER( x, y );
		origin = SUPER_ORIGIN( x, y );
		//%	normal = SUPER_NORMAL( x, y );
		cluster2 = SUPER_CLUSTER( x, y + 1 );
		origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 );
		//%	normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 );
	}
	else if( (y > 0 && bx <= 0.0f) || (y == (lm->sh - 1) && bx >= 0.0f) )
	{
		cluster = SUPER_CLUSTER( x, y - 1 );
		origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 );
		//%	normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 );
		cluster2 = SUPER_CLUSTER( x, y );
		origin2 = SUPER_ORIGIN( x, y );
		//%	normal2 = SUPER_NORMAL( x, y );
	}
	else
		Sys_Printf( "WARNING: Spurious lightmap T vector\n" );
	
	VectorSubtract( origin2, origin, originVecs[ 1 ] );
	//%	VectorSubtract( normal2, normal, normalVecs[ 1 ] );
	
	/* calculate new origin */
	//%	VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin );
	//%	VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin );
	for( i = 0; i < 3; i++ )
		sampleOrigin[ i ] = sampleOrigin[ i ] + (bx * originVecs[ 0 ][ i ]) + (by * originVecs[ 1 ][ i ]);
	
	/* get cluster */
	*sampleCluster = ClusterForPointExtFilter( sampleOrigin, (LUXEL_EPSILON * 2), lm->numLightClusters, lm->lightClusters );
	if( *sampleCluster < 0 )
		return qfalse;
	
	/* calculate new normal */
	//%	VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal );
	//%	VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal );
	//%	if( VectorNormalize( sampleNormal, sampleNormal ) <= 0.0f )
	//%		return qfalse;
	normal = SUPER_NORMAL( x, y );
	VectorCopy( normal, sampleNormal );
	
	/* return ok */
	return qtrue;
}


/*
SubsampleRawLuxel_r()
recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached
*/

static void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel )
{
	int			b, samples, mapped, lighted;
	int			cluster[ 4 ];
	vec4_t		luxel[ 4 ];
	vec3_t		origin[ 4 ], normal[ 4 ];
	float		biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } };
	vec3_t		color, total;
	
	
	/* limit check */
	if( lightLuxel[ 3 ] >= lightSamples )
		return;
	
	/* setup */
	VectorClear( total );
	mapped = 0;
	lighted = 0;
	
	/* make 2x2 subsample stamp */
	for( b = 0; b < 4; b++ )
	{
		/* set origin */
		VectorCopy( sampleOrigin, origin[ b ] );
		
		/* calculate position */
		if( !SubmapRawLuxel( lm, x, y, (bias * biasDirs[ b ][ 0 ]), (bias * biasDirs[ b ][ 1 ]), &cluster[ b ], origin[ b ], normal[ b ] ) )
		{
			cluster[ b ] = -1;
			continue;
		}
		mapped++;
		
		/* increment sample count */
		luxel[ b ][ 3 ] = lightLuxel[ 3 ] + 1.0f;
		
		/* setup trace */
		trace->cluster = *cluster;
		VectorCopy( origin[ b ], trace->origin );
		VectorCopy( normal[ b ], trace->normal );
		
		/* sample light */

		LightContributionToSample( trace );
		
		/* add to totals (fixme: make contrast function) */
		VectorCopy( trace->color, luxel[ b ] );
		VectorAdd( total, trace->color, total );
		if( (luxel[ b ][ 0 ] + luxel[ b ][ 1 ] + luxel[ b ][ 2 ]) > 0.0f )
			lighted++;
	}
	
	/* subsample further? */
	if( (lightLuxel[ 3 ] + 1.0f) < lightSamples &&
		(total[ 0 ] > 4.0f || total[ 1 ] > 4.0f || total[ 2 ] > 4.0f) &&
		lighted != 0 && lighted != mapped )
	{
		for( b = 0; b < 4; b++ )
		{
			if( cluster[ b ] < 0 )
				continue;
			SubsampleRawLuxel_r( lm, trace, origin[ b ], x, y, (bias * 0.25f), luxel[ b ] );
		}
	}
	
	/* average */
	//%	VectorClear( color );
	//%	samples = 0;
	VectorCopy( lightLuxel, color );
	samples = 1;
	for( b = 0; b < 4; b++ )
	{
		if( cluster[ b ] < 0 )
			continue;
		VectorAdd( color, luxel[ b ], color );
		samples++;
	}
	
	/* add to luxel */
	if( samples > 0 )
	{
		/* average */
		color[ 0 ] /= samples;
		color[ 1 ] /= samples;
		color[ 2 ] /= samples;
		
		/* add to color */
		VectorCopy( color, lightLuxel );
		lightLuxel[ 3 ] += 1.0f;
	}
}



/*
IlluminateRawLightmap()
illuminates the luxels
*/

#define STACK_LL_SIZE			(SUPER_LUXEL_SIZE * 64 * 64)
#define LIGHT_LUXEL( x, y )		(lightLuxels + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE))

void IlluminateRawLightmap( int rawLightmapNum )
{
	int					i, t, x, y, sx, sy, size, llSize, luxelFilterRadius, lightmapNum;
	int					*cluster, *cluster2, mapped, lighted, totalLighted;
	rawLightmap_t		*lm;
	surfaceInfo_t		*info;
	qboolean			filterColor, filterDir;
	float				brightness;
	float				*origin, *normal, *dirt, *luxel, *luxel2, *deluxel, *deluxel2;
	float				*lightLuxels, *lightLuxel, samples, filterRadius, weight;
	vec3_t				color, averageColor, averageDir, total, temp, temp2;
	float				tests[ 4 ][ 2 ] = { { 0.0f, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } };
	trace_t				trace;
	float				stackLightLuxels[ STACK_LL_SIZE ];
	vec3_t				flood;
	float				*floodlight;
	
	
	/* bail if this number exceeds the number of raw lightmaps */
	if( rawLightmapNum >= numRawLightmaps )
		return;
	
	/* get lightmap */
	lm = &rawLightmaps[ rawLightmapNum ];
	
	/* setup trace */
	trace.testOcclusion = !noTrace;
	trace.forceSunlight = qfalse;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[ lm->firstLightSurface ];
	trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
	
	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	trace.twoSided = qfalse;
	for( i = 0; i < trace.numSurfaces; i++ )
	{
		/* get surface */
		info = &surfaceInfos[ trace.surfaces[ i ] ];
		
		/* check twosidedness */
		if( info->si->twoSided )
		{
			trace.twoSided = qtrue;
			break;
		}
	}
	
	/* create a culled light list for this raw lightmap */
	CreateTraceLightsForBounds( lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace );
	
	/* -----------------------------------------------------------------
	   fill pass
	   ----------------------------------------------------------------- */
	
	/* set counts */
	numLuxelsIlluminated += (lm->sw * lm->sh);
	
	/* test debugging state */
	if( debugSurfaces || debugAxis || debugCluster || debugOrigin || dirtDebug || normalmap )
	{
		/* debug fill the luxels */
		for( y = 0; y < lm->sh; y++ )
		{
			for( x = 0; x < lm->sw; x++ )
			{
				/* get cluster */
				cluster = SUPER_CLUSTER( x, y );

				/* only fill mapped luxels */
				if( *cluster < 0 )
					continue;
				
				/* get particulars */
				luxel = SUPER_LUXEL( 0, x, y );
				origin = SUPER_ORIGIN( x, y );
				normal = SUPER_NORMAL( x, y );
				
				/* color the luxel with raw lightmap num? */
				if( debugSurfaces )
					VectorCopy( debugColors[ rawLightmapNum % 12 ], luxel );
				
				/* color the luxel with lightmap axis? */
				else if( debugAxis )
				{
					luxel[ 0 ] = (lm->axis[ 0 ] + 1.0f) * 127.5f;
					luxel[ 1 ] = (lm->axis[ 1 ] + 1.0f) * 127.5f;
					luxel[ 2 ] = (lm->axis[ 2 ] + 1.0f) * 127.5f;
				}
				
				/* color the luxel with luxel cluster? */
				else if( debugCluster )
					VectorCopy( debugColors[ *cluster % 12 ], luxel );
				
				/* color the luxel with luxel origin? */
				else if( debugOrigin )
				{
					VectorSubtract( lm->maxs, lm->mins, temp );
					VectorScale( temp, (1.0f / 255.0f), temp );
					VectorSubtract( origin, lm->mins, temp2 );
					luxel[ 0 ] = lm->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]);
					luxel[ 1 ] = lm->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]);
					luxel[ 2 ] = lm->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]);
				}
				
				/* color the luxel with the normal */
				else if( normalmap )
				{
					luxel[ 0 ] = (normal[ 0 ] + 1.0f) * 127.5f;
					luxel[ 1 ] = (normal[ 1 ] + 1.0f) * 127.5f;
					luxel[ 2 ] = (normal[ 2 ] + 1.0f) * 127.5f;
				}
				
				/* otherwise clear it */
				else
					VectorClear( luxel );
				
				/* add to counts */
				luxel[ 3 ] = 1.0f;
			}
		}
	}
	else
	{
		/* allocate temporary per-light luxel storage */
		llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
		if( llSize <= (STACK_LL_SIZE * sizeof( float )) )
			lightLuxels = stackLightLuxels;
		else
			lightLuxels = safe_malloc( llSize );
		
		/* clear luxels */
		//%	memset( lm->superLuxels[ 0 ], 0, llSize );
		
		/* set ambient color */
		for( y = 0; y < lm->sh; y++ )
		{
			for( x = 0; x < lm->sw; x++ )
			{
				/* get cluster */
				cluster = SUPER_CLUSTER( x, y );
				luxel = SUPER_LUXEL( 0, x, y );
				normal = SUPER_NORMAL( x, y );
				deluxel = SUPER_DELUXEL( x, y );
				
				/* blacken unmapped clusters */
				if( *cluster < 0 )
					VectorClear( luxel );
				
				/* set ambient */
				else
				{
					VectorCopy( ambientColor, luxel );
					if( deluxemap )
						VectorScale( normal, 0.00390625f, deluxel );
					luxel[ 3 ] = 1.0f;
				}
			}
		}
		
		/* clear styled lightmaps */
		size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
		for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		{
			if( lm->superLuxels[ lightmapNum ] != NULL )
				memset( lm->superLuxels[ lightmapNum ], 0, size );
		}
		
		/* debugging code */
		//%	if( trace.numLights <= 0 )
		//%		Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] );
		
		/* walk light list */
		for( i = 0; i < trace.numLights; i++ )
		{
			/* setup trace */
			trace.light = trace.lights[ i ];
			
			/* style check */
			for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
			{
				if( lm->styles[ lightmapNum ] == trace.light->style ||
					lm->styles[ lightmapNum ] == LS_NONE )
					break;
			}
			
			/* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */
			if( lightmapNum >= MAX_LIGHTMAPS )
			{
				Sys_Printf( "WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS );
				continue;
			}
			
			/* setup */
			memset( lightLuxels, 0, llSize );
			totalLighted = 0;
			
			/* initial pass, one sample per luxel */
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster */
					cluster = SUPER_CLUSTER( x, y );
					if( *cluster < 0 )
						continue;
					
					/* get particulars */
					lightLuxel = LIGHT_LUXEL( x, y );
					deluxel = SUPER_DELUXEL( x, y );
					origin = SUPER_ORIGIN( x, y );
					normal = SUPER_NORMAL( x, y );

					////////// 27's temp hack for testing edge clipping ////
					if( origin[0]==0 && origin[1]==0 && origin[2]==0 )
					{
						lightLuxel[ 1 ] = 255;
						lightLuxel[ 3 ] = 1.0f;
						totalLighted++;
					}
					else
					{
						/* set contribution count */
						lightLuxel[ 3 ] = 1.0f;
						
						/* setup trace */
						trace.cluster = *cluster;
						VectorCopy( origin, trace.origin );
						VectorCopy( normal, trace.normal );
						
						/* get light for this sample */
						LightContributionToSample( &trace );
						VectorCopy( trace.color, lightLuxel );
						
						/* add to count */
						if( trace.color[ 0 ] || trace.color[ 1 ] || trace.color[ 2 ] )
							totalLighted++;
					}
					
					/* add to light direction map (fixme: use luxel normal as starting point for deluxel?) */
					if( deluxemap )
					{
						/* color to grayscale (photoshop rgb weighting) */
						brightness = trace.color[ 0 ] * 0.3f + trace.color[ 1 ] * 0.59f + trace.color[ 2 ] * 0.11f;
						brightness *= (1.0 / 255.0);
						VectorScale( trace.direction, brightness, trace.direction );
						VectorAdd( deluxel, trace.direction, deluxel );
					}
				}
			}
			
			/* don't even bother with everything else if nothing was lit */
			if( totalLighted == 0 )
				continue;
			
			/* determine filter radius */
			filterRadius = lm->filterRadius > trace.light->filterRadius
				? lm->filterRadius
				: trace.light->filterRadius;
			if( filterRadius < 0.0f )
				filterRadius = 0.0f;
			
			/* set luxel filter radius */
			luxelFilterRadius = superSample * filterRadius / lm->sampleSize;
			if( luxelFilterRadius == 0 && (filterRadius > 0.0f || filter) )
				luxelFilterRadius = 1;
			
			/* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */
			/* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */
			if( lightSamples > 1 && luxelFilterRadius == 0 )
			{
				/* walk luxels */
				for( y = 0; y < (lm->sh - 1); y++ )
				{
					for( x = 0; x < (lm->sw - 1); x++ )
					{
						/* setup */
						mapped = 0;
						lighted = 0;
						VectorClear( total );
						
						/* test 2x2 stamp */
						for( t = 0; t < 4; t++ )
						{
							/* set sample coords */
							sx = x + tests[ t ][ 0 ];
							sy = y + tests[ t ][ 1 ];
							
							/* get cluster */
							cluster = SUPER_CLUSTER( sx, sy );
							if( *cluster < 0 )
								continue;
							mapped++;
							
							/* get luxel */
							lightLuxel = LIGHT_LUXEL( sx, sy );
							VectorAdd( total, lightLuxel, total );
							if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) > 0.0f )
								lighted++;
						}
						
						/* if total color is under a certain amount, then don't bother subsampling */
						if( total[ 0 ] <= 4.0f && total[ 1 ] <= 4.0f && total[ 2 ] <= 4.0f )
							continue;
						
						/* if all 4 pixels are either in shadow or light, then don't subsample */
						if( lighted != 0 && lighted != mapped )
						{
							for( t = 0; t < 4; t++ )
							{
								/* set sample coords */
								sx = x + tests[ t ][ 0 ];
								sy = y + tests[ t ][ 1 ];
								
								/* get luxel */
								cluster = SUPER_CLUSTER( sx, sy );
								if( *cluster < 0 )
									continue;
								lightLuxel = LIGHT_LUXEL( sx, sy );
								origin = SUPER_ORIGIN( sx, sy );
								
								/* only subsample shadowed luxels */
								//%	if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f )
								//%		continue;
								
								/* subsample it */
								SubsampleRawLuxel_r( lm, &trace, origin, sx, sy, 0.25f, lightLuxel );
								
								/* debug code to colorize subsampled areas to yellow */
								//%	luxel = SUPER_LUXEL( lightmapNum, sx, sy );
								//%	VectorSet( luxel, 255, 204, 0 );
							}
						}
					}
				}
			}
			
			/* tertiary pass, apply dirt map (ambient occlusion) */
			if( 0 && dirty )
			{
				/* walk luxels */
				for( y = 0; y < lm->sh; y++ )
				{
					for( x = 0; x < lm->sw; x++ )
					{
						/* get cluster  */
						cluster = SUPER_CLUSTER( x, y );
						if( *cluster < 0 )
							continue;
						
						/* get particulars */
						lightLuxel = LIGHT_LUXEL( x, y );
						dirt = SUPER_DIRT( x, y );
						
						/* scale light value */
						VectorScale( lightLuxel, *dirt, lightLuxel );
					}
				}
			}
			
			/* allocate sampling lightmap storage */
			if( lm->superLuxels[ lightmapNum ] == NULL )
			{
				/* allocate sampling lightmap storage */
				size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float );
				lm->superLuxels[ lightmapNum ] = safe_malloc( size );
				memset( lm->superLuxels[ lightmapNum ], 0, size );
			}
			
			/* set style */
			if( lightmapNum > 0 )
			{
				lm->styles[ lightmapNum ] = trace.light->style;
				//%	Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style );
			}
			
			/* copy to permanent luxels */
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster and origin */
					cluster = SUPER_CLUSTER( x, y );
					if( *cluster < 0 )
						continue;
					origin = SUPER_ORIGIN( x, y );
					
					/* filter? */
					if( luxelFilterRadius )
					{
						/* setup */
						VectorClear( averageColor );
						samples = 0.0f;
						
						/* cheaper distance-based filtering */
						for( sy = (y - luxelFilterRadius); sy <= (y + luxelFilterRadius); sy++ )
						{
							if( sy < 0 || sy >= lm->sh )
								continue;
							
							for( sx = (x - luxelFilterRadius); sx <= (x + luxelFilterRadius); sx++ )
							{
								if( sx < 0 || sx >= lm->sw )
									continue;
								
								/* get particulars */
								cluster = SUPER_CLUSTER( sx, sy );
								if( *cluster < 0 )
									continue;
								lightLuxel = LIGHT_LUXEL( sx, sy );
								
								/* create weight */
								weight = (abs( sx - x ) == luxelFilterRadius ? 0.5f : 1.0f);
								weight *= (abs( sy - y ) == luxelFilterRadius ? 0.5f : 1.0f);
								
								/* scale luxel by filter weight */
								VectorScale( lightLuxel, weight, color );
								VectorAdd( averageColor, color, averageColor );
								samples += weight;
							}
						}
						
						/* any samples? */
						if( samples <= 0.0f	)
							continue;
						
						/* scale into luxel */
						luxel = SUPER_LUXEL( lightmapNum, x, y );
						luxel[ 3 ] = 1.0f;
						
						/* handle negative light */
						if( trace.light->flags & LIGHT_NEGATIVE )
						{ 
							luxel[ 0 ] -= averageColor[ 0 ] / samples;
							luxel[ 1 ] -= averageColor[ 1 ] / samples;
							luxel[ 2 ] -= averageColor[ 2 ] / samples;
						}
						
						/* handle normal light */
						else
						{ 
							luxel[ 0 ] += averageColor[ 0 ] / samples;
							luxel[ 1 ] += averageColor[ 1 ] / samples;
							luxel[ 2 ] += averageColor[ 2 ] / samples;
						}
					}
					
					/* single sample */
					else
					{
						/* get particulars */
						lightLuxel = LIGHT_LUXEL( x, y );
						luxel = SUPER_LUXEL( lightmapNum, x, y );
						
						/* handle negative light */
						if( trace.light->flags & LIGHT_NEGATIVE )
							VectorScale( averageColor, -1.0f, averageColor );

						/* add color */
						luxel[ 3 ] = 1.0f;
						
						/* handle negative light */
						if( trace.light->flags & LIGHT_NEGATIVE )
							VectorSubtract( luxel, lightLuxel, luxel );
						
						/* handle normal light */
						else
							VectorAdd( luxel, lightLuxel, luxel );
					}
				}
			}
		}
		
		/* free temporary luxels */
		if( lightLuxels != stackLightLuxels )
			free( lightLuxels );
	}
	
	/* free light list */
	FreeTraceLights( &trace );
	
	/*	-----------------------------------------------------------------
		floodlight pass
		----------------------------------------------------------------- */

	if( floodlighty )
	{
		/* walk lightmaps */
		for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		{
			/* early out */
			if( lm->superLuxels[ lightmapNum ] == NULL )
				continue;
			
			/* apply floodlight to each luxel */
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster */
					cluster	= SUPER_CLUSTER( x, y );
					//%	if( *cluster < 0 )
					//%		continue;
					
					/* get particulars */
					luxel = SUPER_LUXEL( lightmapNum, x, y );
					floodlight = SUPER_FLOODLIGHT( x, y );
					
					flood[0]=floodlightRGB[0]*floodlightIntensity;
					flood[1]=floodlightRGB[1]*floodlightIntensity;
					flood[2]=floodlightRGB[2]*floodlightIntensity;
					             
					/* scale light value */
					VectorScale( flood, *floodlight, flood );
					luxel[0]+=flood[0];
					luxel[1]+=flood[1];
					luxel[2]+=flood[2];
					
					if (luxel[3]==0) luxel[3]=1;
				}
			}
		}
	}

	if (debugnormals)
	{
		for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		{
			/* early out */
			if( lm->superLuxels[ lightmapNum ] == NULL )
				continue;
			
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster */
					cluster	= SUPER_CLUSTER( x, y );
					//%	if( *cluster < 0 )
					//%		continue;
					
					/* get particulars */
					luxel = SUPER_LUXEL( lightmapNum, x, y );
					normal = SUPER_NORMAL (  x, y );
               
					luxel[0]=(normal[0]*127)+127;
					luxel[1]=(normal[1]*127)+127;
					luxel[2]=(normal[2]*127)+127;
				}
			}
		}
	}
	
	/*	-----------------------------------------------------------------
		dirt pass
		----------------------------------------------------------------- */
	
	if( dirty )
	{
		/* walk lightmaps */
		for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		{
			/* early out */
			if( lm->superLuxels[ lightmapNum ] == NULL )
				continue;
			
			/* apply dirt to each luxel */
			for( y = 0; y < lm->sh; y++ )
			{
				for( x = 0; x < lm->sw; x++ )
				{
					/* get cluster */
					cluster	= SUPER_CLUSTER( x, y );
					//%	if( *cluster < 0 )
					//%		continue;
					
					/* get particulars */
					luxel = SUPER_LUXEL( lightmapNum, x, y );
					dirt = SUPER_DIRT( x, y );
					
					/* apply dirt */
					VectorScale( luxel, *dirt, luxel );
					
					/* debugging */
					if( dirtDebug )
						VectorSet( luxel, *dirt * 255.0f, *dirt * 255.0f, *dirt * 255.0f );
				}
			}
		}
	}
	
	/* -----------------------------------------------------------------
	   filter pass
	   ----------------------------------------------------------------- */
	
	/* walk lightmaps */
	for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
	{
		/* early out */
		if( lm->superLuxels[ lightmapNum ] == NULL )
			continue;
		
		/* average occluded luxels from neighbors */
		for( y = 0; y < lm->sh; y++ )
		{
			for( x = 0; x < lm->sw; x++ )
			{
				/* get particulars */
				cluster = SUPER_CLUSTER( x, y );
				luxel = SUPER_LUXEL( lightmapNum, x, y );
				deluxel = SUPER_DELUXEL( x, y );
				normal = SUPER_NORMAL( x, y );
				
				/* determine if filtering is necessary */
				filterColor = qfalse;
				filterDir = qfalse;
				if( *cluster < 0 ||
					(lm->splotchFix && (luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ])) )
					filterColor = qtrue;
				if( deluxemap && lightmapNum == 0 && (*cluster < 0 || filter) )
					filterDir = qtrue;
				
				if( !filterColor && !filterDir )
					continue;
				
				/* choose seed amount */
				VectorClear( averageColor );
				VectorClear( averageDir );
				samples = 0.0f;
				
				/* walk 3x3 matrix */
				for( sy = (y - 1); sy <= (y + 1); sy++ )
				{
					if( sy < 0 || sy >= lm->sh )
						continue;
					
					for( sx = (x - 1); sx <= (x + 1); sx++ )
					{
						if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) )
							continue;
						
						/* get neighbor's particulars */
						cluster2 = SUPER_CLUSTER( sx, sy );
						luxel2 = SUPER_LUXEL( lightmapNum, sx, sy );
						deluxel2 = SUPER_DELUXEL( sx, sy );
						
						/* ignore unmapped/unlit luxels */
						if( *cluster2 < 0 || luxel2[ 3 ] == 0.0f ||
							(lm->splotchFix && VectorCompare( luxel2, ambientColor )) )
							continue;
						
						/* add its distinctiveness to our own */
						VectorAdd( averageColor, luxel2, averageColor );
						samples += luxel2[ 3 ];
						if( filterDir )
							VectorAdd( averageDir, deluxel2, averageDir );
					}
				}
				
				/* fall through */
				if( samples <= 0.0f )
					continue;
				
				/* dark lightmap seams */
				if( dark )
				{
					if( lightmapNum == 0 )
						VectorMA( averageColor, 2.0f, ambientColor, averageColor );
					samples += 2.0f;
				}
				
				/* average it */
				if( filterColor )
				{
					VectorDivide( averageColor, samples, luxel );
					luxel[ 3 ] = 1.0f;
				}
				if( filterDir )
					VectorDivide( averageDir, samples, deluxel );
				
				/* set cluster to -3 */
				if( *cluster < 0 )
					*cluster = CLUSTER_FLOODED;
			}
		}
	}
}



/*
IlluminateVertexes()
light the surface vertexes
*/

#define VERTEX_NUDGE	4.0f

void IlluminateVertexes( int num )
{
	int					i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster;
	int					lightmapNum, numAvg;
	float				samples, *vertLuxel, *radVertLuxel, *luxel, dirt;
	vec3_t				origin, temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ];
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	rawLightmap_t		*lm;
	bspDrawVert_t		*verts;
	trace_t				trace;
	
	
	/* get surface, info, and raw lightmap */
	ds = &bspDrawSurfaces[ num ];
	info = &surfaceInfos[ num ];
	lm = info->lm;
	
	/* -----------------------------------------------------------------
	   illuminate the vertexes
	   ----------------------------------------------------------------- */
	
	/* calculate vertex lighting for surfaces without lightmaps */
	if( lm == NULL || cpmaHack )
	{
		/* setup trace */
		trace.testOcclusion = (cpmaHack && lm != NULL) ? qfalse : !noTrace;
		trace.forceSunlight = info->si->forceSunlight;
		trace.recvShadows = info->recvShadows;
		trace.numSurfaces = 1;
		trace.surfaces = &num;
		trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
		
		/* twosided lighting */
		trace.twoSided = info->si->twoSided;
		
		/* make light list for this surface */
		CreateTraceLightsForSurface( num, &trace );
		
		/* setup */
		verts = yDrawVerts + ds->firstVert;
		numAvg = 0;
		memset( avgColors, 0, sizeof( avgColors ) );
		
		/* walk the surface verts */
		for( i = 0; i < ds->numVerts; i++ )
		{
			/* get vertex luxel */
			radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
			
			/* color the luxel with raw lightmap num? */
			if( debugSurfaces )
				VectorCopy( debugColors[ num % 12 ], radVertLuxel );
			
			/* color the luxel with luxel origin? */
			else if( debugOrigin )
			{
				VectorSubtract( info->maxs, info->mins, temp );
				VectorScale( temp, (1.0f / 255.0f), temp );
				VectorSubtract( origin, lm->mins, temp2 );
				radVertLuxel[ 0 ] = info->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]);
				radVertLuxel[ 1 ] = info->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]);
				radVertLuxel[ 2 ] = info->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]);
			}
			
			/* color the luxel with the normal */
			else if( normalmap )
			{
				radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f;
				radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f;
				radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f;
			}
			
			/* illuminate the vertex */
			else
			{
				/* clear vertex luxel */
				VectorSet( radVertLuxel, -1.0f, -1.0f, -1.0f );
				
				/* try at initial origin */
				trace.cluster = ClusterForPointExtFilter( verts[ i ].xyz, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
				if( trace.cluster >= 0 )
				{
					/* setup trace */
					VectorCopy( verts[ i ].xyz, trace.origin );
					VectorCopy( verts[ i ].normal, trace.normal );
					
					/* r7 dirt */
					if( dirty )
						dirt = DirtForSample( &trace );
					else
						dirt = 1.0f;

					/* trace */
					LightingAtSample( &trace, ds->vertexStyles, colors );
					
					/* store */
					for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
					{
						/* r7 dirt */
						VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] );
						
						/* store */
						radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
						VectorCopy( colors[ lightmapNum ], radVertLuxel );
						VectorAdd( avgColors[ lightmapNum ], colors[ lightmapNum ], colors[ lightmapNum ] );
					}
				}
				
				/* is this sample bright enough? */
				radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
				if( radVertLuxel[ 0 ] <= ambientColor[ 0 ] &&
					radVertLuxel[ 1 ] <= ambientColor[ 1 ] &&
					radVertLuxel[ 2 ] <= ambientColor[ 2 ] )
				{
					/* nudge the sample point around a bit */
					for( x = 0; x < 4; x++ )
					{
						/* two's complement 0, 1, -1, 2, -2, etc */
						x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);
						
						for( y = 0; y < 4; y++ )
						{
							y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);
							
							for( z = 0; z < 4; z++ )
							{
								z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);
								
								/* nudge origin */
								trace.origin[ 0 ] = verts[ i ].xyz[ 0 ] + (VERTEX_NUDGE * x1);
								trace.origin[ 1 ] = verts[ i ].xyz[ 1 ] + (VERTEX_NUDGE * y1);
								trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + (VERTEX_NUDGE * z1);
								
								/* try at nudged origin */
								trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
								if( trace.cluster < 0 )
									continue;
															
								/* trace */
								LightingAtSample( &trace, ds->vertexStyles, colors );
								
								/* store */
								for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
								{
									/* r7 dirt */
									VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] );
									
									/* store */
									radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
									VectorCopy( colors[ lightmapNum ], radVertLuxel );
								}
								
								/* bright enough? */
								radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
								if( radVertLuxel[ 0 ] > ambientColor[ 0 ] ||
									radVertLuxel[ 1 ] > ambientColor[ 1 ] ||
									radVertLuxel[ 2 ] > ambientColor[ 2 ] )
									x = y = z = 1000;
							}
						}
					}
				}
				
				/* add to average? */
				radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
				if( radVertLuxel[ 0 ] > ambientColor[ 0 ] ||
					radVertLuxel[ 1 ] > ambientColor[ 1 ] ||
					radVertLuxel[ 2 ] > ambientColor[ 2 ] )
				{
					numAvg++;
					for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
					{
						radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
						VectorAdd( avgColors[ lightmapNum ], radVertLuxel, avgColors[ lightmapNum ] );
					}
				}
			}
			
			/* another happy customer */
			numVertsIlluminated++;
		}
		
		/* set average color */
		if( numAvg > 0 )
		{
			for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
				VectorScale( avgColors[ lightmapNum ], (1.0f / numAvg), avgColors[ lightmapNum ] );
		}
		else
		{
			VectorCopy( ambientColor, avgColors[ 0 ] );
		}
		
		/* clean up and store vertex color */
		for( i = 0; i < ds->numVerts; i++ )
		{
			/* get vertex luxel */
			radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i );
			
			/* store average in occluded vertexes */
			if( radVertLuxel[ 0 ] < 0.0f )
			{
				for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
				{
					radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
					VectorCopy( avgColors[ lightmapNum ], radVertLuxel );
					
					/* debug code */
					//%	VectorSet( radVertLuxel, 255.0f, 0.0f, 0.0f );
				}
			}
			
			/* store it */
			for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
			{
				/* get luxels */
				vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
				radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
				
				/* store */
				if( bouncing || bounce == 0 || !bounceOnly )
					VectorAdd( vertLuxel, radVertLuxel, vertLuxel );
				if( !info->si->noVertexLight )
					ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], info->si->vertexScale );
			}
		}
		
		/* free light list */
		FreeTraceLights( &trace );
		
		/* return to sender */
		return;
	}
	
	/* -----------------------------------------------------------------
	   reconstitute vertex lighting from the luxels
	   ----------------------------------------------------------------- */
	
	/* set styles from lightmap */
	for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		ds->vertexStyles[ lightmapNum ] = lm->styles[ lightmapNum ];
	
	/* get max search radius */
	maxRadius = lm->sw;
	maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh;
	
	/* walk the surface verts */
	verts = yDrawVerts + ds->firstVert;
	for( i = 0; i < ds->numVerts; i++ )
	{
		/* do each lightmap */
		for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
		{
			/* early out */
			if( lm->superLuxels[ lightmapNum ] == NULL )
				continue;
			
			/* get luxel coords */
			x = verts[ i ].lightmap[ lightmapNum ][ 0 ];
			y = verts[ i ].lightmap[ lightmapNum ][ 1 ];
			if( x < 0 )
				x = 0;
			else if( x >= lm->sw )
				x = lm->sw - 1;
			if( y < 0 )
				y = 0;
			else if( y >= lm->sh )
				y = lm->sh - 1;
			
			/* get vertex luxels */
			vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
			radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i );
			
			/* color the luxel with the normal? */
			if( normalmap )
			{
				radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f;
				radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f;
				radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f;
			}
			
			/* color the luxel with surface num? */
			else if( debugSurfaces )
				VectorCopy( debugColors[ num % 12 ], radVertLuxel );
			
			/* divine color from the superluxels */
			else
			{
				/* increasing radius */
				VectorClear( radVertLuxel );
				samples = 0.0f;
				for( radius = 0; radius < maxRadius && samples <= 0.0f; radius++ )
				{
					/* sample within radius */
					for( sy = (y - radius); sy <= (y + radius); sy++ )
					{
						if( sy < 0 || sy >= lm->sh )
							continue;
						
						for( sx = (x - radius); sx <= (x + radius); sx++ )
						{
							if( sx < 0 || sx >= lm->sw )
								continue;
							
							/* get luxel particulars */
							luxel = SUPER_LUXEL( lightmapNum, sx, sy );
							cluster = SUPER_CLUSTER( sx, sy );
							if( *cluster < 0 )
								continue;
							
							/* testing: must be brigher than ambient color */
							//%	if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] )
							//%		continue;
							
							/* add its distinctiveness to our own */
							VectorAdd( radVertLuxel, luxel, radVertLuxel );
							samples += luxel[ 3 ];
						}
					}
				}
				
				/* any color? */
				if( samples > 0.0f )
					VectorDivide( radVertLuxel, samples, radVertLuxel );
				else
					VectorCopy( ambientColor, radVertLuxel );
			}
			
			/* store into floating point storage */
			VectorAdd( vertLuxel, radVertLuxel, vertLuxel );
			numVertsIlluminated++;
			
			/* store into bytes (for vertex approximation) */
			if( !info->si->noVertexLight )
				ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], 1.0f );
		}
	}
}



/* -------------------------------------------------------------------------------

light optimization (-fast)

creates a list of lights that will affect a surface and stores it in tw
this is to optimize surface lighting by culling out as many of the
lights in the world as possible from further calculation

------------------------------------------------------------------------------- */

/*
SetupBrushes()
determines opaque brushes in the world and find sky shaders for sunlight calculations
*/

void SetupBrushes( void )
{
	int				i, j, b, compileFlags;
	qboolean		inside;
	bspBrush_t		*brush;
	bspBrushSide_t	*side;
	bspShader_t		*shader;
	shaderInfo_t	*si;
	
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "--- SetupBrushes ---\n" );
	
	/* allocate */
	if( opaqueBrushes == NULL )
		opaqueBrushes = safe_malloc( numBSPBrushes / 8 + 1 );
	
	/* clear */
	memset( opaqueBrushes, 0, numBSPBrushes / 8 + 1 );
	numOpaqueBrushes = 0;
	
	/* walk the list of worldspawn brushes */
	for( i = 0; i < bspModels[ 0 ].numBSPBrushes; i++ )
	{
		/* get brush */
		b = bspModels[ 0 ].firstBSPBrush + i;
		brush = &bspBrushes[ b ];
		
		/* check all sides */
		inside = qtrue;
		compileFlags = 0;
		for( j = 0; j < brush->numSides && inside; j++ )
		{
			/* do bsp shader calculations */
			side = &bspBrushSides[ brush->firstSide + j ];
			shader = &bspShaders[ side->shaderNum ];
			
			/* get shader info */
			si = ShaderInfoForShader( shader->shader );
			if( si == NULL )
				continue;
			
			/* or together compile flags */
			compileFlags |= si->compileFlags;
		}
		
		/* determine if this brush is opaque to light */
		if( !(compileFlags & C_TRANSLUCENT) )
		{
			opaqueBrushes[ b >> 3 ] |= (1 << (b & 7));
			numOpaqueBrushes++;
			maxOpaqueBrush = i;
		}
	}
	
	/* emit some statistics */
	Sys_FPrintf( SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes );
}



/*
ClusterVisible()
determines if two clusters are visible to each other using the PVS
*/

qboolean ClusterVisible( int a, int b )
{
	int			portalClusters, leafBytes;
	byte		*pvs;
	
	
	/* dummy check */
	if( a < 0 || b < 0 )
		return qfalse;
	
	/* early out */
	if( a == b )
		return qtrue;
	
	/* not vised? */
	if( numBSPVisBytes <=8 )
		return qtrue;
	
	/* get pvs data */
	portalClusters = ((int *) bspVisBytes)[ 0 ];
	leafBytes = ((int*) bspVisBytes)[ 1 ];
	pvs = bspVisBytes + VIS_HEADER_SIZE + (a * leafBytes);
	
	/* check */
	if( (pvs[ b >> 3 ] & (1 << (b & 7))) )
		return qtrue;
	return qfalse;
}



/*
PointInLeafNum_r()
borrowed from vlight.c
*/

int	PointInLeafNum_r( vec3_t point, int nodenum )
{
	int			leafnum;
	vec_t		dist;
	bspNode_t		*node;
	bspPlane_t	*plane;
	
	
	while( nodenum >= 0 )
	{
		node = &bspNodes[ nodenum ];
		plane = &bspPlanes[ node->planeNum ];
		dist = DotProduct( point, plane->normal ) - plane->dist;
		if( dist > 0.1 )
			nodenum = node->children[ 0 ];
		else if( dist < -0.1 )
			nodenum = node->children[ 1 ];
		else
		{
			leafnum = PointInLeafNum_r( point, node->children[ 0 ] );
			if( bspLeafs[ leafnum ].cluster != -1 )
				return leafnum;
			nodenum = node->children[ 1 ];
		}
	}
	
	leafnum = -nodenum - 1;
	return leafnum;
}



/*
PointInLeafnum()
borrowed from vlight.c
*/

int	PointInLeafNum( vec3_t point )
{
	return PointInLeafNum_r( point, 0 );
}



/*
ClusterVisibleToPoint() - ydnar
returns qtrue if point can "see" cluster
*/

qboolean ClusterVisibleToPoint( vec3_t point, int cluster )
{
	int		pointCluster;
	

	/* get leafNum for point */
	pointCluster = ClusterForPoint( point );
	if( pointCluster < 0 )
		return qfalse;
	
	/* check pvs */
	return ClusterVisible( pointCluster, cluster );
}



/*
ClusterForPoint() - ydnar
returns the pvs cluster for point
*/

int ClusterForPoint( vec3_t point )
{
	int		leafNum;
	

	/* get leafNum for point */
	leafNum = PointInLeafNum( point );
	if( leafNum < 0 )
		return -1;
	
	/* return the cluster */
	return bspLeafs[ leafNum ].cluster;
}



/*
ClusterForPointExt() - ydnar
also takes brushes into account for occlusion testing
*/

int ClusterForPointExt( vec3_t point, float epsilon )
{
	int				i, j, b, leafNum, cluster;
	float			dot;
	qboolean		inside;
	int				*brushes, numBSPBrushes;
	bspLeaf_t		*leaf;
	bspBrush_t		*brush;
	bspPlane_t		*plane;
	
	
	/* get leaf for point */
	leafNum = PointInLeafNum( point );
	if( leafNum < 0 )
		return -1;
	leaf = &bspLeafs[ leafNum ];
	
	/* get the cluster */
	cluster = leaf->cluster;
	if( cluster < 0 )
		return -1;
	
	/* transparent leaf, so check point against all brushes in the leaf */
	brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ];
	numBSPBrushes = leaf->numBSPLeafBrushes;
	for( i = 0; i < numBSPBrushes; i++ )
	{
		/* get parts */
		b = brushes[ i ];
		if( b > maxOpaqueBrush )
			continue;
		brush = &bspBrushes[ b ];
		if( !(opaqueBrushes[ b >> 3 ] & (1 << (b & 7))) )
			continue;
		
		/* check point against all planes */
		inside = qtrue;
		for( j = 0; j < brush->numSides && inside; j++ )
		{
			plane = &bspPlanes[ bspBrushSides[ brush->firstSide + j ].planeNum ];
			dot = DotProduct( point, plane->normal );
			dot -= plane->dist;
			if( dot > epsilon )
				inside = qfalse;
		}
		
		/* if inside, return bogus cluster */
		if( inside )
			return -1 - b;
	}
	
	/* if the point made it this far, it's not inside any opaque brushes */
	return cluster;
}



/*
ClusterForPointExtFilter() - ydnar
adds cluster checking against a list of known valid clusters
*/

int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters )
{
	int		i, cluster;
	
	
	/* get cluster for point */
	cluster = ClusterForPointExt( point, epsilon );
	
	/* check if filtering is necessary */
	if( cluster < 0 || numClusters <= 0 || clusters == NULL )
		return cluster;
	
	/* filter */
	for( i = 0; i < numClusters; i++ )
	{
		if( cluster == clusters[ i ] || ClusterVisible( cluster, clusters[ i ] ) )
			return cluster;
	}
	
	/* failed */
	return -1;
}



/*
ShaderForPointInLeaf() - ydnar
checks a point against all brushes in a leaf, returning the shader of the brush
also sets the cumulative surface and content flags for the brush hit
*/

int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags )
{
	int				i, j;
	float			dot;
	qboolean		inside;
	int				*brushes, numBSPBrushes;
	bspLeaf_t			*leaf;
	bspBrush_t		*brush;
	bspBrushSide_t	*side;
	bspPlane_t		*plane;
	bspShader_t		*shader;
	int				allSurfaceFlags, allContentFlags;

	
	/* clear things out first */
	*surfaceFlags = 0;
	*contentFlags = 0;
	
	/* get leaf */
	if( leafNum < 0 )
		return -1;
	leaf = &bspLeafs[ leafNum ];
	
	/* transparent leaf, so check point against all brushes in the leaf */
	brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ];
	numBSPBrushes = leaf->numBSPLeafBrushes;
	for( i = 0; i < numBSPBrushes; i++ )
	{
		/* get parts */
		brush = &bspBrushes[ brushes[ i ] ];
		
		/* check point against all planes */
		inside = qtrue;
		allSurfaceFlags = 0;
		allContentFlags = 0;
		for( j = 0; j < brush->numSides && inside; j++ )
		{
			side = &bspBrushSides[ brush->firstSide + j ];
			plane = &bspPlanes[ side->planeNum ];
			dot = DotProduct( point, plane->normal );
			dot -= plane->dist;
			if( dot > epsilon )
				inside = qfalse;
			else
			{
				shader = &bspShaders[ side->shaderNum ];
				allSurfaceFlags |= shader->surfaceFlags;
				allContentFlags |= shader->contentFlags;
			}
		}
		
		/* handle if inside */
		if( inside )
		{
			/* if there are desired flags, check for same and continue if they aren't matched */
			if( wantContentFlags && !(wantContentFlags & allContentFlags) )
				continue;
			if( wantSurfaceFlags && !(wantSurfaceFlags & allSurfaceFlags) )
				continue;
			
			/* store the cumulative flags and return the brush shader (which is mostly useless) */
			*surfaceFlags = allSurfaceFlags;
			*contentFlags = allContentFlags;
			return brush->shaderNum;
		}
	}
	
	/* if the point made it this far, it's not inside any brushes */
	return -1;
}



/*
ChopBounds()
chops a bounding box by the plane defined by origin and normal
returns qfalse if the bounds is entirely clipped away

this is not exactly the fastest way to do this...
*/

qboolean ChopBounds( vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal )
{
	/* FIXME: rewrite this so it doesn't use bloody brushes */
	return qtrue;
}



/*
SetupEnvelopes()
calculates each light's effective envelope,
taking into account brightness, type, and pvs.
*/

#define LIGHT_EPSILON	0.125f
#define LIGHT_NUDGE		2.0f

void SetupEnvelopes( qboolean forGrid, qboolean fastFlag )
{
	int			i, x, y, z, x1, y1, z1;
	light_t		*light, *light2, **owner;
	bspLeaf_t	*leaf;
	vec3_t		origin, dir, mins, maxs;
	float		radius, intensity;
	light_t		*buckets[ 256 ];
	
	
	/* early out for weird cases where there are no lights */
	if( lights == NULL )
		return;
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "" );
	
	/* count lights */
	numLights = 0;
	numCulledLights = 0;
	owner = &lights;
	while( *owner != NULL )
	{
		/* get light */
		light = *owner;
		
		/* handle negative lights */
		if( light->photons < 0.0f || light->add < 0.0f )
		{
			light->photons *= -1.0f;
			light->add *= -1.0f;
			light->flags |= LIGHT_NEGATIVE;
		}
		
		/* sunlight? */
		if( light->type == EMIT_SUN )
		{
			/* special cased */
			light->cluster = 0;
			light->envelope = MAX_WORLD_COORD * 8.0f;
			VectorSet( light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f );
			VectorSet( light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f );
		}
		
		/* everything else */
		else
		{
			/* get pvs cluster for light */
			light->cluster = ClusterForPointExt( light->origin, LIGHT_EPSILON );
			
			/* invalid cluster? */
			if( light->cluster < 0 )
			{
				/* nudge the sample point around a bit */
				for( x = 0; x < 4; x++ )
				{
					/* two's complement 0, 1, -1, 2, -2, etc */
					x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1);
					
					for( y = 0; y < 4; y++ )
					{
						y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1);
						
						for( z = 0; z < 4; z++ )
						{
							z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1);
							
							/* nudge origin */
							origin[ 0 ] = light->origin[ 0 ] + (LIGHT_NUDGE * x1);
							origin[ 1 ] = light->origin[ 1 ] + (LIGHT_NUDGE * y1);
							origin[ 2 ] = light->origin[ 2 ] + (LIGHT_NUDGE * z1);
							
							/* try at nudged origin */
							light->cluster = ClusterForPointExt( origin, LIGHT_EPSILON );
							if( light->cluster < 0 )
								continue;
									
							/* set origin */
							VectorCopy( origin, light->origin );
						}
					}
				}
			}
			
			/* only calculate for lights in pvs and outside of opaque brushes */
			if( light->cluster >= 0 )
			{
				/* set light fast flag */
				if( fastFlag )
					light->flags |= LIGHT_FAST_TEMP;
				else
					light->flags &= ~LIGHT_FAST_TEMP;
				if( light->si && light->si->noFast )
					light->flags &= ~(LIGHT_FAST | LIGHT_FAST_TEMP);
				
				/* clear light envelope */
				light->envelope = 0;
				
				/* handle area lights */
				if( exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL )
				{
					/* ugly hack to calculate extent for area lights, but only done once */
					VectorScale( light->normal, -1.0f, dir );
					for( radius = 100.0f; radius < 130000.0f && light->envelope == 0; radius += 10.0f )
					{
						float	factor;
						
						VectorMA( light->origin, radius, light->normal, origin );
						factor = PointToPolygonFormFactor( origin, dir, light->w );
						if( factor < 0.0f )
							factor *= -1.0f;
						if( (factor * light->add) <= light->falloffTolerance )
							light->envelope = radius;
					}
					
					/* check for fast mode */
					if( !(light->flags & LIGHT_FAST) && !(light->flags & LIGHT_FAST_TEMP) )
						light->envelope = MAX_WORLD_COORD * 8.0f;
				}
				else
				{
					radius = 0.0f;
					intensity = light->photons;
				}
				
				/* other calcs */
				if( light->envelope <= 0.0f )
				{
					/* solve distance for non-distance lights */
					if( !(light->flags & LIGHT_ATTEN_DISTANCE) )
						light->envelope = MAX_WORLD_COORD * 8.0f;
					
					/* solve distance for linear lights */
					else if( (light->flags & LIGHT_ATTEN_LINEAR ) )
						//% light->envelope = ((intensity / light->falloffTolerance) * linearScale - 1 + radius) / light->fade;
						light->envelope = ((intensity * linearScale) - light->falloffTolerance) / light->fade;

						/*
						add = angle * light->photons * linearScale - (dist * light->fade);
						T = (light->photons * linearScale) - (dist * light->fade);
						T + (dist * light->fade) = (light->photons * linearScale);
						dist * light->fade = (light->photons * linearScale) - T;
						dist = ((light->photons * linearScale) - T) / light->fade;
						*/
					
					/* solve for inverse square falloff */
					else
						light->envelope = sqrt( intensity / light->falloffTolerance ) + radius;
						
						/*
						add = light->photons / (dist * dist);
						T = light->photons / (dist * dist);
						T * (dist * dist) = light->photons;
						dist = sqrt( light->photons / T );
						*/
				}
				
				/* chop radius against pvs */
				{
					/* clear bounds */
					ClearBounds( mins, maxs );
					
					/* check all leaves */
					for( i = 0; i < numBSPLeafs; i++ )
					{
						/* get test leaf */
						leaf = &bspLeafs[ i ];
						
						/* in pvs? */
						if( leaf->cluster < 0 )
							continue;
						if( ClusterVisible( light->cluster, leaf->cluster ) == qfalse )	/* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */
							continue;
						
						/* add this leafs bbox to the bounds */
						VectorCopy( leaf->mins, origin );
						AddPointToBounds( origin, mins, maxs );
						VectorCopy( leaf->maxs, origin );
						AddPointToBounds( origin, mins, maxs );
					}
					
					/* test to see if bounds encompass light */
					for( i = 0; i < 3; i++ )
					{
						if( mins[ i ] > light->origin[ i ] || maxs[ i ] < light->origin[ i ] )
						{
							//% Sys_Printf( "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n",
							//% 	mins[ 0 ], mins[ 1 ], mins[ 2 ],
							//% 	maxs[ 0 ], maxs[ 1 ], maxs[ 2 ],
							//% 	numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] );
							AddPointToBounds( light->origin, mins, maxs );
						}
					}
					
					/* chop the bounds by a plane for area lights and spotlights */
					if( light->type == EMIT_AREA || light->type == EMIT_SPOT )
						ChopBounds( mins, maxs, light->origin, light->normal );
					
					/* copy bounds */
					VectorCopy( mins, light->mins );
					VectorCopy( maxs, light->maxs );
					
					/* reflect bounds around light origin */
					//%	VectorMA( light->origin, -1.0f, origin, origin );
					VectorScale( light->origin, 2, origin );
					VectorSubtract( origin, maxs, origin );
					AddPointToBounds( origin, mins, maxs );
					//%	VectorMA( light->origin, -1.0f, mins, origin );
					VectorScale( light->origin, 2, origin );
					VectorSubtract( origin, mins, origin );
					AddPointToBounds( origin, mins, maxs );
					 
					/* calculate spherical bounds */
					VectorSubtract( maxs, light->origin, dir );
					radius = (float) VectorLength( dir );
					
					/* if this radius is smaller than the envelope, then set the envelope to it */
					if( radius < light->envelope )
					{
						light->envelope = radius;
						//%	Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights );
					}
					//%	else
					//%		Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope );
				}
				
				/* add grid/surface only check */
				if( forGrid )
				{
					if( !(light->flags & LIGHT_GRID) )
						light->envelope = 0.0f;
				}
				else
				{
					if( !(light->flags & LIGHT_SURFACES) )
						light->envelope = 0.0f;
				}
			}
			
			/* culled? */
			if( light->cluster < 0 || light->envelope <= 0.0f )
			{
				/* debug code */
				//%	Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope );
				
				/* delete the light */
				numCulledLights++;
				*owner = light->next;
				if( light->w != NULL )
					free( light->w );
				free( light );
				continue;
			}
		}
		
		/* square envelope */
		light->envelope2 = (light->envelope * light->envelope);
		
		/* increment light count */
		numLights++;
		
		/* set next light */
		owner = &((**owner).next);
	}
	
	/* bucket sort lights by style */
	memset( buckets, 0, sizeof( buckets ) );
	light2 = NULL;
	for( light = lights; light != NULL; light = light2 )
	{
		/* get next light */
		light2 = light->next;
		
		/* filter into correct bucket */
		light->next = buckets[ light->style ];
		buckets[ light->style ] = light;
		
		/* if any styled light is present, automatically set nocollapse */
		if( light->style != LS_NORMAL )
			noCollapse = qtrue;
	}
	
	/* filter back into light list */
	lights = NULL;
	for( i = 255; i >= 0; i-- )
	{
		light2 = NULL;
		for( light = buckets[ i ]; light != NULL; light = light2 )
		{
			light2 = light->next;
			light->next = lights;
			lights = light;
		}
	}
	
	/* emit some statistics */
	Sys_Printf( "%9d total lights\n", numLights );
	Sys_Printf( "%9d culled lights\n", numCulledLights );
}



/*
CreateTraceLightsForBounds()
creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves)
*/

void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace )
{
	int			i;
	light_t		*light;
	vec3_t		origin, dir, nullVector = { 0.0f, 0.0f, 0.0f };
	float		radius, dist, length;
	
	
	/* potential pre-setup  */
	if( numLights == 0 )
		SetupEnvelopes( qfalse, fast );
	
	/* debug code */
	//% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] );
	
	/* allocate the light list */
	trace->lights = safe_malloc( sizeof( light_t* ) * (numLights + 1) );
	trace->numLights = 0;
	
	/* calculate spherical bounds */
	VectorAdd( mins, maxs, origin );
	VectorScale( origin, 0.5f, origin );
	VectorSubtract( maxs, origin, dir );
	radius = (float) VectorLength( dir );
	
	/* get length of normal vector */
	if( normal != NULL )
		length = VectorLength( normal );
	else
	{
		normal = nullVector;
		length = 0;
	}
	
	/* test each light and see if it reaches the sphere */
	/* note: the attenuation code MUST match LightingAtSample() */
	for( light = lights; light; light = light->next )
	{
		/* check zero sized envelope */
		if( light->envelope <= 0 )
		{
			lightsEnvelopeCulled++;
			continue;
		}
		
		/* check flags */
		if( !(light->flags & flags) )
			continue;
		
		/* sunlight skips all this nonsense */
		if( light->type != EMIT_SUN )
		{
			/* sun only? */
			if( sunOnly )
				continue;
			
			/* check against pvs cluster */
			if( numClusters > 0 && clusters != NULL )
			{
				for( i = 0; i < numClusters; i++ )
				{
					if( ClusterVisible( light->cluster, clusters[ i ] ) )
						break;
				}
				
				/* fixme! */
				if( i == numClusters )
				{
					lightsClusterCulled++;
					continue;
				}
			}
			
			/* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */
			VectorSubtract( light->origin, origin, dir );
			dist = VectorLength( dir );
			dist -= light->envelope;
			dist -= radius;
			if( dist > 0 )
			{
				lightsEnvelopeCulled++;
				continue;
			}
			
			/* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */
			#if 0
			skip = qfalse;
			for( i = 0; i < 3; i++ )
			{
				if( mins[ i ] > light->maxs[ i ] || maxs[ i ] < light->mins[ i ] )
					skip = qtrue;
			}
			if( skip )
			{
				lightsBoundsCulled++;
				continue;
			}
			#endif
		}
		
		/* planar surfaces (except twosided surfaces) have a couple more checks */
		if( length > 0.0f && trace->twoSided == qfalse )
		{
			/* lights coplanar with a surface won't light it */
			if( !(light->flags & LIGHT_TWOSIDED) && DotProduct( light->normal, normal ) > 0.999f )
			{
				lightsPlaneCulled++;
				continue;
			}
			
			/* check to see if light is behind the plane */
			if( DotProduct( light->origin, normal ) - DotProduct( origin, normal ) < -1.0f )
			{
				lightsPlaneCulled++;
				continue;
			}
		}
		
		/* add this light */
		trace->lights[ trace->numLights++ ] = light;
	}
	
	/* make last night null */
	trace->lights[ trace->numLights ] = NULL;
}



void FreeTraceLights( trace_t *trace )
{
	if( trace->lights != NULL )
		free( trace->lights );
}



/*
CreateTraceLightsForSurface()
creates a list of lights that can potentially affect a drawsurface
*/

void CreateTraceLightsForSurface( int num, trace_t *trace )
{
	int					i;
	vec3_t				mins, maxs, normal;
	bspDrawVert_t		*dv;
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	
	
	/* dummy check */
	if( num < 0 )
		return;
	
	/* get drawsurface and info */
	ds = &bspDrawSurfaces[ num ];
	info = &surfaceInfos[ num ];
	
	/* get the mins/maxs for the dsurf */
	ClearBounds( mins, maxs );
	VectorCopy( bspDrawVerts[ ds->firstVert ].normal, normal );
	for( i = 0; i < ds->numVerts; i++ )
	{
		dv = &yDrawVerts[ ds->firstVert + i ];
		AddPointToBounds( dv->xyz, mins, maxs );
		if( !VectorCompare( dv->normal, normal ) )
			VectorClear( normal );
	}
	
	/* create the lights for the bounding box */
	CreateTraceLightsForBounds( mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ], LIGHT_SURFACES, trace );
}

/////////////////////////////////////////////////////////////

#define FLOODLIGHT_CONE_ANGLE			88	/* degrees */
#define FLOODLIGHT_NUM_ANGLE_STEPS		16
#define FLOODLIGHT_NUM_ELEVATION_STEPS	4
#define FLOODLIGHT_NUM_VECTORS			(FLOODLIGHT_NUM_ANGLE_STEPS * FLOODLIGHT_NUM_ELEVATION_STEPS)

static vec3_t	floodVectors[ FLOODLIGHT_NUM_VECTORS ];
static int		numFloodVectors = 0;

void SetupFloodLight( void )
{
	int		i, j;
	float	angle, elevation, angleStep, elevationStep;
	const char	*value;
	double v1,v2,v3,v4,v5;
	
	/* note it */
	Sys_FPrintf( SYS_VRB, "--- SetupFloodLight ---\n" );
	
	/* calculate angular steps */
	angleStep = DEG2RAD( 360.0f / FLOODLIGHT_NUM_ANGLE_STEPS );
	elevationStep = DEG2RAD( FLOODLIGHT_CONE_ANGLE / FLOODLIGHT_NUM_ELEVATION_STEPS );
	
	/* iterate angle */
	angle = 0.0f;
	for( i = 0, angle = 0.0f; i < FLOODLIGHT_NUM_ANGLE_STEPS; i++, angle += angleStep )
	{
		/* iterate elevation */
		for( j = 0, elevation = elevationStep * 0.5f; j < FLOODLIGHT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep )
		{
			floodVectors[ numFloodVectors ][ 0 ] = sin( elevation ) * cos( angle );
			floodVectors[ numFloodVectors ][ 1 ] = sin( elevation ) * sin( angle );
			floodVectors[ numFloodVectors ][ 2 ] = cos( elevation );
			numFloodVectors++;
		}
	}
	
	/* emit some statistics */
	Sys_FPrintf( SYS_VRB, "%9d numFloodVectors\n", numFloodVectors );

      /* floodlight */
	value = ValueForKey( &entities[ 0 ], "_floodlight" );
	
	if( value[ 0 ] != '\0' )
	{
		v1=v2=v3=0;
		v4=floodlightDistance;
		v5=floodlightIntensity;
		
		sscanf( value, "%lf %lf %lf %lf %lf", &v1, &v2, &v3, &v4, &v5);
		
		floodlightRGB[0]=v1;
		floodlightRGB[1]=v2;
		floodlightRGB[2]=v3;
		
		if (VectorLength(floodlightRGB)==0)
		{
			VectorSet(floodlightRGB,240,240,255);
		}
		
		if (v4<1) v4=1024;
		if (v5<1) v5=128;
		
		floodlightDistance=v4;
		floodlightIntensity=v5;
    
		floodlighty = qtrue;
		Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" );
	}
	else
	{
		VectorSet(floodlightRGB,240,240,255);
		//floodlighty = qtrue;
		//Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" );
	}
	VectorNormalize(floodlightRGB,floodlightRGB);
}

//27 - lighttracer style ambient occlusion light hack.
//Kudos to the dirtmapping author for most of this source.
void FloodLightRawLightmap( int rawLightmapNum )
{
	int					i, x, y, sx, sy, *cluster;
	float				*origin, *normal, *floodlight, *floodlight2, average, samples;
	rawLightmap_t		*lm;
	surfaceInfo_t		*info;
	trace_t				trace;
	
	/* bail if this number exceeds the number of raw lightmaps */
	if( rawLightmapNum >= numRawLightmaps )
		return;
	
	/* get lightmap */
	lm = &rawLightmaps[ rawLightmapNum ];
	
	memset(&trace,0,sizeof(trace_t));
	/* setup trace */
	trace.testOcclusion = qtrue;
	trace.forceSunlight = qfalse;
	trace.twoSided = qtrue;
	trace.recvShadows = lm->recvShadows;
	trace.numSurfaces = lm->numLightSurfaces;
	trace.surfaces = &lightSurfaces[ lm->firstLightSurface ];
	trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS;
	trace.testAll = qfalse;
	trace.distance = 1024;
	
	/* twosided lighting (may or may not be a good idea for lightmapped stuff) */
	//trace.twoSided = qfalse;
	for( i = 0; i < trace.numSurfaces; i++ )
	{
		/* get surface */
		info = &surfaceInfos[ trace.surfaces[ i ] ];
		
		/* check twosidedness */
		if( info->si->twoSided )
		{
			trace.twoSided = qtrue;
			break;
		}
	}
	
	/* gather dirt */
	for( y = 0; y < lm->sh; y++ )
	{
		for( x = 0; x < lm->sw; x++ )
		{
			/* get luxel */
			cluster = SUPER_CLUSTER( x, y );
			origin = SUPER_ORIGIN( x, y );
			normal = SUPER_NORMAL( x, y );
			floodlight = SUPER_FLOODLIGHT( x, y );
			
			/* set default dirt */
			*floodlight = 0.0f;
			
			/* only look at mapped luxels */
			if( *cluster < 0 )
				continue;
			
			/* copy to trace */
			trace.cluster = *cluster;
			VectorCopy( origin, trace.origin );
			VectorCopy( normal, trace.normal );
         

		
			/* get dirt */
			*floodlight = FloodLightForSample( &trace );
		}
	}
	
}

/*
FloodLightForSample()
calculates floodlight value for a given sample
once again, kudos to the dirtmapping coder
*/
float FloodLightForSample( trace_t *trace )
{
	int		i;
	float 	d;
	float 	contribution;
	int 	sub = 0;
	float	gatherLight, outLight;
	vec3_t	normal, worldUp, myUp, myRt, direction, displacement;
	float 	dd;
	int 	vecs = 0;
 
	gatherLight=0;
	/* dummy check */
	//if( !dirty )
	//	return 1.0f;
	if( trace == NULL || trace->cluster < 0 )
		return 0.0f;
	

	/* setup */
	dd = floodlightDistance;
	VectorCopy( trace->normal, normal );
	
	/* check if the normal is aligned to the world-up */
	if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f )
	{
		if( normal[ 2 ] == 1.0f )		
		{
			VectorSet( myRt, 1.0f, 0.0f, 0.0f );
			VectorSet( myUp, 0.0f, 1.0f, 0.0f );
		}
		else if( normal[ 2 ] == -1.0f )
		{
			VectorSet( myRt, -1.0f, 0.0f, 0.0f );
			VectorSet( myUp,  0.0f, 1.0f, 0.0f );
		}
	}
	else
	{
		VectorSet( worldUp, 0.0f, 0.0f, 1.0f );
		CrossProduct( normal, worldUp, myRt );
		VectorNormalize( myRt, myRt );
		CrossProduct( myRt, normal, myUp );
		VectorNormalize( myUp, myUp );
	}

	/* iterate through ordered vectors */
	for( i = 0; i < numFloodVectors; i++ )
	{
		if (floodlight_lowquality==qtrue)
        {
			if (rand()%10 != 0 ) continue;
		}

		vecs++;
         
		/* transform vector into tangent space */
		direction[ 0 ] = myRt[ 0 ] * floodVectors[ i ][ 0 ] + myUp[ 0 ] * floodVectors[ i ][ 1 ] + normal[ 0 ] * floodVectors[ i ][ 2 ];
		direction[ 1 ] = myRt[ 1 ] * floodVectors[ i ][ 0 ] + myUp[ 1 ] * floodVectors[ i ][ 1 ] + normal[ 1 ] * floodVectors[ i ][ 2 ];
		direction[ 2 ] = myRt[ 2 ] * floodVectors[ i ][ 0 ] + myUp[ 2 ] * floodVectors[ i ][ 1 ] + normal[ 2 ] * floodVectors[ i ][ 2 ];

		/* set endpoint */
		VectorMA( trace->origin, dd, direction, trace->end );

		//VectorMA( trace->origin, 1, direction, trace->origin );
			
		SetupTrace( trace );
		/* trace */
	  	TraceLine( trace );
		contribution=1;

		if (trace->compileFlags & C_SKY )
		{
			contribution=1.0f;
		}
		else if ( trace->opaque )
		{
			VectorSubtract( trace->hit, trace->origin, displacement );
			d=VectorLength( displacement );

			// d=trace->distance;            
			//if (d>256) gatherDirt+=1;
			contribution=d/dd;
			if (contribution>1) contribution=1.0f; 
             
			//gatherDirt += 1.0f - ooDepth * VectorLength( displacement );
		}
         
		gatherLight+=contribution;
	}
   
	/* early out */
	if( gatherLight <= 0.0f )
		return 0.0f;
   	
	sub=vecs;

	if (sub<1) sub=1;
	gatherLight/=(sub);

	outLight=gatherLight;
	if( outLight > 1.0f )
		outLight = 1.0f;
	
	/* return to sender */
	return outLight;
}

